summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore34
-rw-r--r--.gitmodules9
-rw-r--r--AUTHORS1
-rw-r--r--ChangeLog12
-rw-r--r--Makefile.am5
-rw-r--r--README98
-rwxr-xr-xbootstrap20
-rw-r--r--configure.ac225
-rw-r--r--contrib/.gitignore3
-rw-r--r--contrib/Makefile.am70
-rw-r--r--contrib/Makefile.am.in68
-rw-r--r--contrib/auditor-report.tex.j2157
-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)12
-rw-r--r--contrib/exchange-tos-bfh-v0.rst307
-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.rst (renamed from contrib/tos/tos.rst)104
m---------contrib/gana0
-rwxr-xr-xcontrib/gana-generate.sh41
-rwxr-xr-xcontrib/gana-latest.sh (renamed from contrib/gana.sh)2
-rwxr-xr-xcontrib/gana-update.sh34
-rw-r--r--contrib/gnunet.tag20
-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.tag42
-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.conf (renamed from debian/etc-taler-exchange/taler/secrets/exchange-accountcredentials.secret.conf)0
-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/Makefile109
-rw-r--r--contrib/pp/README58
-rw-r--r--contrib/pp/conf.py282
-rw-r--r--contrib/pp/en/0.epubbin14983 -> 0 bytes
-rw-r--r--contrib/pp/en/0.html211
-rw-r--r--contrib/pp/en/0.pdfbin77445 -> 0 bytes
-rw-r--r--contrib/pp/en/0.txt237
-rw-r--r--contrib/pp/en/0.xml214
-rw-r--r--contrib/pp/locale/de/LC_MESSAGES/pp.po221
-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-testing27
-rwxr-xr-xcontrib/taler-exchange-dbconfig186
-rwxr-xr-xcontrib/taler-nexus-prepare128
-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.epubbin17320 -> 0 bytes
-rw-r--r--contrib/tos/en/0.html304
-rw-r--r--contrib/tos/en/0.pdfbin82060 -> 0 bytes
-rw-r--r--contrib/tos/en/0.txt337
-rw-r--r--contrib/tos/en/0.xml311
-rw-r--r--contrib/tos/locale/de/LC_MESSAGES/tos.po241
-rwxr-xr-xcontrib/uncrustify_precommit5
-rwxr-xr-xcontrib/update-pp.sh14
-rwxr-xr-xcontrib/update-tos.sh15
m---------contrib/wallet-core0
-rw-r--r--debian/changelog148
-rw-r--r--debian/control94
-rw-r--r--debian/etc-libtalerexchange/taler/taler.conf12
-rw-r--r--debian/etc-taler-auditor/nginx/sites-available/taler-auditor23
-rw-r--r--debian/etc-taler-auditor/taler/secrets/auditor-db.secret.conf2
-rw-r--r--debian/etc-taler-exchange/nginx/sites-available/taler-exchange7
-rw-r--r--debian/etc-taler-exchange/taler/conf.d/exchange-business.conf25
-rw-r--r--debian/etc-taler-exchange/taler/conf.d/exchange-system.conf3
-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.conf2
-rw-r--r--debian/libtalerexchange-dev.install18
-rw-r--r--debian/libtalerexchange.install1
-rw-r--r--debian/libtalerexchange.postinst28
-rwxr-xr-xdebian/rules26
-rw-r--r--debian/taler-auditor.install6
-rw-r--r--debian/taler-auditor.postinst5
-rw-r--r--debian/taler-auditor.postrm9
-rw-r--r--debian/taler-auditor.taler-auditor-httpd.service3
-rw-r--r--debian/taler-auditor.taler-helper-auditor-deposits.service15
-rw-r--r--debian/taler-exchange-database.install2
-rw-r--r--debian/taler-exchange-offline.postinst13
-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.install26
-rw-r--r--debian/taler-exchange.postinst18
-rw-r--r--debian/taler-exchange.postrm39
-rw-r--r--debian/taler-exchange.taler-exchange-aggregator.service9
-rw-r--r--debian/taler-exchange.taler-exchange-aggregator@.service24
-rw-r--r--debian/taler-exchange.taler-exchange-closer.service9
-rw-r--r--debian/taler-exchange.taler-exchange-expire.service20
-rw-r--r--debian/taler-exchange.taler-exchange-httpd.service23
-rw-r--r--debian/taler-exchange.taler-exchange-httpd@.service11
-rw-r--r--debian/taler-exchange.taler-exchange-secmod-cs.service19
-rw-r--r--debian/taler-exchange.taler-exchange-secmod-eddsa.service5
-rw-r--r--debian/taler-exchange.taler-exchange-secmod-rsa.service5
-rw-r--r--debian/taler-exchange.taler-exchange-transfer.service10
-rw-r--r--debian/taler-exchange.taler-exchange-wirewatch.service10
-rw-r--r--debian/taler-exchange.taler-exchange-wirewatch@.service13
-rw-r--r--debian/taler-exchange.taler-exchange.slice7
-rw-r--r--debian/taler-exchange.taler-exchange.target1
-rw-r--r--debian/taler-exchange.tmpfiles3
-rw-r--r--debian/taler-terms-generator.install8
-rw-r--r--doc/.gitignore4
-rw-r--r--doc/Makefile.am44
-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.doxy2551
-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
m---------doc/prebuilt0
-rw-r--r--doc/system/.gitignore2
-rw-r--r--doc/system/plots/cpu.pdfbin0 -> 11450 bytes
-rw-r--r--doc/system/plots/latencies.pdfbin0 -> 11163 bytes
-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/req-received.data7
-rw-r--r--doc/system/plots/req-sent.data7
-rw-r--r--doc/system/plots/speed.pdfbin0 -> 8161 bytes
-rw-r--r--doc/system/system.tex3
-rw-r--r--doc/system/taler-arch-full.pdfbin0 -> 293976 bytes
-rw-r--r--doc/system/taler/implementation.tex2
-rw-r--r--m4/ax_compare_version.m4177
-rw-r--r--m4/ax_lib_postgresql.m4272
-rw-r--r--m4/libgnurl.m4266
-rw-r--r--m4/mhd.m449
-rw-r--r--src/Makefile.am4
-rw-r--r--src/auditor/.gitignore7
-rw-r--r--src/auditor/Makefile.am70
-rw-r--r--src/auditor/auditor-basedb.age1
-rw-r--r--src/auditor/auditor-basedb.feesbin800 -> 0 bytes
-rw-r--r--src/auditor/auditor-basedb.mpub1
-rw-r--r--src/auditor/auditor-basedb.sql8597
-rw-r--r--src/auditor/auditor.conf6
-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.conf186
-rwxr-xr-xsrc/auditor/generate-auditor-basedb.sh301
-rw-r--r--src/auditor/generate-kyc-basedb.conf4
-rwxr-xr-xsrc/auditor/generate-revoke-basedb.sh377
-rw-r--r--src/auditor/generate_auditordb_home/.local/share/taler/exchange-offline/master.priv1
-rw-r--r--src/auditor/report-lib.c287
-rw-r--r--src/auditor/report-lib.h65
-rw-r--r--src/auditor/revoke-basedb.age1
-rw-r--r--src/auditor/revoke-basedb.conf (renamed from src/auditor/test-auditor.conf)14
-rw-r--r--src/auditor/revoke-basedb.feesbin800 -> 0 bytes
-rw-r--r--src/auditor/revoke-basedb.mpub1
-rw-r--r--src/auditor/revoke-basedb.sql8606
-rwxr-xr-xsrc/auditor/setup.sh93
-rw-r--r--src/auditor/taler-auditor-dbinit.c4
-rw-r--r--src/auditor/taler-auditor-exchange.c224
-rw-r--r--src/auditor/taler-auditor-httpd.c76
-rw-r--r--src/auditor/taler-auditor-httpd.h6
-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.c231
-rw-r--r--src/auditor/taler-auditor-httpd_deposit-confirmation.h1
-rw-r--r--src/auditor/taler-auditor-httpd_exchanges.c116
-rw-r--r--src/auditor/taler-auditor-sync.c43
-rw-r--r--src/auditor/taler-auditor.in20
-rw-r--r--src/auditor/taler-helper-auditor-aggregation.c417
-rw-r--r--src/auditor/taler-helper-auditor-coins.c1480
-rw-r--r--src/auditor/taler-helper-auditor-deposits.c239
-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.c1251
-rw-r--r--src/auditor/taler-helper-auditor-wire.c1327
-rwxr-xr-xsrc/auditor/test-auditor.sh2636
-rwxr-xr-xsrc/auditor/test-kyc.sh751
-rwxr-xr-xsrc/auditor/test-revocation.sh889
-rwxr-xr-xsrc/auditor/test-sync.sh183
-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.am90
-rw-r--r--src/auditordb/auditor-0001.sql505
-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.c2869
-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.c723
-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.am34
-rw-r--r--src/bank-lib/bank_api_admin.c52
-rw-r--r--src/bank-lib/bank_api_common.c4
-rw-r--r--src/bank-lib/bank_api_common.h2
-rw-r--r--src/bank-lib/bank_api_credit.c149
-rw-r--r--src/bank-lib/bank_api_debit.c144
-rw-r--r--src/bank-lib/bank_api_transfer.c88
-rw-r--r--src/bank-lib/fakebank.c2510
-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.c282
-rw-r--r--src/bank-lib/taler-fakebank-run.c77
-rwxr-xr-xsrc/bank-lib/test_bank.sh49
-rw-r--r--src/benchmark/.gitignore1
-rw-r--r--src/benchmark/Makefile.am13
-rw-r--r--src/benchmark/bank-benchmark-cs.conf5
-rw-r--r--src/benchmark/bank-benchmark-rsa.conf5
-rw-r--r--src/benchmark/bank-benchmark.conf128
-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.conf126
-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.c199
-rw-r--r--src/benchmark/taler-bank-benchmark.c630
-rw-r--r--src/benchmark/taler-exchange-benchmark.c928
-rw-r--r--src/curl/Makefile.am2
-rw-r--r--src/curl/curl.c82
-rw-r--r--src/exchange-tools/Makefile.am19
-rw-r--r--src/exchange-tools/exchange-offline.conf4
-rw-r--r--src/exchange-tools/taler-auditor-offline.c486
-rw-r--r--src/exchange-tools/taler-crypto-worker.c266
-rw-r--r--src/exchange-tools/taler-exchange-dbinit.c48
-rw-r--r--src/exchange-tools/taler-exchange-offline.c2500
-rw-r--r--src/exchange/.gitignore2
-rw-r--r--src/exchange/Makefile.am92
-rw-r--r--src/exchange/exchange.conf98
-rw-r--r--src/exchange/taler-exchange-aggregator.c1214
-rw-r--r--src/exchange/taler-exchange-closer.c135
-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.c1566
-rw-r--r--src/exchange/taler-exchange-httpd.h201
-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.c12
-rw-r--r--src/exchange/taler-exchange-httpd_auditors.h2
-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)22
-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.h (renamed from src/exchange/taler-exchange-httpd_withdraw.h)41
-rw-r--r--src/exchange/taler-exchange-httpd_db.c85
-rw-r--r--src/exchange/taler-exchange-httpd_db.h15
-rw-r--r--src/exchange/taler-exchange-httpd_deposit.c474
-rw-r--r--src/exchange/taler-exchange-httpd_deposits_get.c522
-rw-r--r--src/exchange/taler-exchange-httpd_deposits_get.h7
-rw-r--r--src/exchange/taler-exchange-httpd_extensions.c461
-rw-r--r--src/exchange/taler-exchange-httpd_extensions.h15
-rw-r--r--src/exchange/taler-exchange-httpd_keys.c2365
-rw-r--r--src/exchange/taler-exchange-httpd_keys.h243
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-check.c518
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-check.h4
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-proof.c837
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-proof.h4
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-wallet.c149
-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.c41
-rw-r--r--src/exchange/taler-exchange-httpd_link.h4
-rw-r--r--src/exchange/taler-exchange-httpd_management.h54
-rw-r--r--src/exchange/taler-exchange-httpd_management_aml-officers.c142
-rw-r--r--src/exchange/taler-exchange-httpd_management_auditors.c4
-rw-r--r--src/exchange/taler-exchange-httpd_management_auditors_AP_disable.c2
-rw-r--r--src/exchange/taler-exchange-httpd_management_denominations_HDP_revoke.c3
-rw-r--r--src/exchange/taler-exchange-httpd_management_drain.c195
-rw-r--r--src/exchange/taler-exchange-httpd_management_extensions.c309
-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.c328
-rw-r--r--src/exchange/taler-exchange-httpd_management_signkey_EP_revoke.c1
-rw-r--r--src/exchange/taler-exchange-httpd_management_wire_disable.c18
-rw-r--r--src/exchange/taler-exchange-httpd_management_wire_enable.c116
-rw-r--r--src/exchange/taler-exchange-httpd_management_wire_fees.c45
-rw-r--r--src/exchange/taler-exchange-httpd_melt.c99
-rw-r--r--src/exchange/taler-exchange-httpd_melt.h6
-rw-r--r--src/exchange/taler-exchange-httpd_metrics.c99
-rw-r--r--src/exchange/taler-exchange-httpd_metrics.h86
-rw-r--r--src/exchange/taler-exchange-httpd_mhd.h2
-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/auditor/taler-auditor-httpd_exchanges.h)35
-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.c68
-rw-r--r--src/exchange/taler-exchange-httpd_recoup-refresh.h2
-rw-r--r--src/exchange/taler-exchange-httpd_recoup.c77
-rw-r--r--src/exchange/taler-exchange-httpd_refreshes_reveal.c585
-rw-r--r--src/exchange/taler-exchange-httpd_refund.c44
-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.c299
-rw-r--r--src/exchange/taler-exchange-httpd_reserves_get.h9
-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.c992
-rw-r--r--src/exchange/taler-exchange-httpd_responses.h232
-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_transfers_get.c210
-rw-r--r--src/exchange/taler-exchange-httpd_wire.c425
-rw-r--r--src/exchange/taler-exchange-httpd_wire.h70
-rw-r--r--src/exchange/taler-exchange-httpd_withdraw.c543
-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.c41
-rw-r--r--src/exchange/taler-exchange-wirewatch.c1063
-rw-r--r--src/exchange/test_taler_exchange_httpd.conf46
-rwxr-xr-xsrc/exchange/test_taler_exchange_httpd.sh6
-rw-r--r--src/exchange/test_taler_exchange_unix.conf42
-rw-r--r--src/exchangedb/.gitignore19
-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.am327
-rw-r--r--src/exchangedb/auditor-triggers-0001.sql41
-rw-r--r--src/exchangedb/bench-db-postgres.conf4
-rw-r--r--src/exchangedb/bench_db.c30
-rw-r--r--src/exchangedb/benchmark-0000.sql293
-rw-r--r--src/exchangedb/drop.sql39
-rw-r--r--src/exchangedb/drop0001.sql75
-rw-r--r--src/exchangedb/exchange-0001.sql2060
-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.conf8
-rw-r--r--src/exchangedb/exchangedb_accounts.c1
-rw-r--r--src/exchangedb/exchangedb_transactions.c55
-rw-r--r--src/exchangedb/irbt_callbacks.c691
-rw-r--r--src/exchangedb/lrbt_callbacks.c1302
-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.h (renamed from src/util/extensions.c)47
-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.c86
-rw-r--r--src/exchangedb/plugin_exchangedb_common.h51
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c12089
-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.conf6
-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.c1085
-rw-r--r--src/exchangedb/test_exchangedb_by_j.c232
-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/exchangedb/withdraw.sql130
-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.am5
-rw-r--r--src/include/platform.h219
-rw-r--r--src/include/taler_amount_lib.h28
-rw-r--r--src/include/taler_attributes.h129
-rw-r--r--src/include/taler_auditor_service.h216
-rw-r--r--src/include/taler_auditordb_plugin.h1091
-rw-r--r--src/include/taler_bank_service.h294
-rw-r--r--src/include/taler_crypto_lib.h4197
-rw-r--r--src/include/taler_curl_lib.h18
-rw-r--r--src/include/taler_exchange_service.h5469
-rw-r--r--src/include/taler_exchangedb_lib.h7
-rw-r--r--src/include/taler_exchangedb_plugin.h4238
-rw-r--r--src/include/taler_extensions.h378
-rw-r--r--src/include/taler_extensions_policy.h205
-rw-r--r--src/include/taler_fakebank_lib.h29
-rw-r--r--src/include/taler_json_lib.h444
-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.h404
-rw-r--r--src/include/taler_pq_lib.h288
-rw-r--r--src/include/taler_signatures.h1731
-rw-r--r--src/include/taler_templating_lib.h130
-rw-r--r--src/include/taler_testing_lib.h1778
-rw-r--r--src/include/taler_util.h415
-rw-r--r--src/json/Makefile.am18
-rw-r--r--src/json/json.c243
-rw-r--r--src/json/json_helper.c1331
-rw-r--r--src/json/json_pack.c248
-rw-r--r--src/json/json_wire.c78
-rw-r--r--src/json/test_json.c68
-rw-r--r--src/json/test_json_wire.c80
-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.am66
-rw-r--r--src/lib/auditor_api_curl_defaults.c14
-rw-r--r--src/lib/auditor_api_deposit_confirmation.c200
-rw-r--r--src/lib/auditor_api_exchanges.c270
-rw-r--r--src/lib/auditor_api_get_config.c278
-rw-r--r--src/lib/auditor_api_handle.c547
-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.c49
-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.c1368
-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.c15
-rw-r--r--src/lib/exchange_api_curl_defaults.h1
-rw-r--r--src/lib/exchange_api_deposit.c753
-rw-r--r--src/lib/exchange_api_deposits_get.c222
-rw-r--r--src/lib/exchange_api_handle.c2726
-rw-r--r--src/lib/exchange_api_handle.h226
-rw-r--r--src/lib/exchange_api_kyc_check.c120
-rw-r--r--src/lib/exchange_api_kyc_proof.c56
-rw-r--r--src/lib/exchange_api_kyc_wallet.c75
-rw-r--r--src/lib/exchange_api_link.c252
-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.c39
-rw-r--r--src/lib/exchange_api_management_auditor_enable.c50
-rw-r--r--src/lib/exchange_api_management_drain_profits.c213
-rw-r--r--src/lib/exchange_api_management_get_keys.c145
-rw-r--r--src/lib/exchange_api_management_post_extensions.c134
-rw-r--r--src/lib/exchange_api_management_post_keys.c39
-rw-r--r--src/lib/exchange_api_management_revoke_denomination_key.c39
-rw-r--r--src/lib/exchange_api_management_revoke_signing_key.c36
-rw-r--r--src/lib/exchange_api_management_set_global_fee.c236
-rw-r--r--src/lib/exchange_api_management_set_wire_fee.c66
-rw-r--r--src/lib/exchange_api_management_update_aml_officer.c230
-rw-r--r--src/lib/exchange_api_management_wire_disable.c57
-rw-r--r--src/lib/exchange_api_management_wire_enable.c86
-rw-r--r--src/lib/exchange_api_melt.c620
-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.c233
-rw-r--r--src/lib/exchange_api_recoup_refresh.c243
-rw-r--r--src/lib/exchange_api_refresh_common.c546
-rw-r--r--src/lib/exchange_api_refresh_common.h106
-rw-r--r--src/lib/exchange_api_refreshes_reveal.c320
-rw-r--r--src/lib/exchange_api_refund.c506
-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.c154
-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.c215
-rw-r--r--src/lib/exchange_api_wire.c483
-rw-r--r--src/lib/exchange_api_withdraw.c218
-rw-r--r--src/lib/exchange_api_withdraw2.c513
-rw-r--r--src/lib/test_stefan.c206
-rw-r--r--src/mhd/Makefile.am4
-rw-r--r--src/mhd/mhd_config.c2
-rw-r--r--src/mhd/mhd_legal.c119
-rw-r--r--src/mhd/mhd_parsing.c408
-rw-r--r--src/mhd/mhd_responses.c45
-rw-r--r--src/mhd/mhd_run.c4
-rw-r--r--src/pq/Makefile.am2
-rw-r--r--src/pq/pq_common.c68
-rw-r--r--src/pq/pq_common.h148
-rw-r--r--src/pq/pq_query_helper.c1082
-rw-r--r--src/pq/pq_result_helper.c1235
-rw-r--r--src/pq/test_pq.c215
-rw-r--r--src/sq/sq_query_helper.c2
-rw-r--r--src/sq/sq_result_helper.c2
-rw-r--r--src/sq/test_sq.c8
-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/.gitignore33
-rw-r--r--src/testing/Makefile.am374
-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.conf50
-rw-r--r--src/testing/test-taler-exchange-wirewatch-postgres.conf52
-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.c264
-rw-r--r--src/testing/test_auditor_api.conf140
-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.c45
-rw-r--r--src/testing/test_bank_api.c248
-rw-r--r--src/testing/test_bank_api.conf23
-rw-r--r--src/testing/test_bank_api_fakebank.conf22
-rw-r--r--src/testing/test_bank_api_fakebank_twisted.conf24
-rw-r--r--src/testing/test_bank_api_nexus.conf38
-rw-r--r--src/testing/test_bank_api_pybank.conf21
-rw-r--r--src/testing/test_bank_api_pybank_twisted.conf43
-rw-r--r--src/testing/test_bank_api_twisted.c220
-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.conf29
-rw-r--r--src/testing/test_exchange_api.c631
-rw-r--r--src/testing/test_exchange_api.conf175
-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.privbin32 -> 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_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.c112
-rw-r--r--src/testing/test_exchange_api_keys_cherry_picking.conf84
-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/master.priv1
-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.c95
-rw-r--r--src/testing/test_exchange_api_revocation.c125
-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.c262
-rw-r--r--src/testing/test_exchange_api_twisted.conf150
-rw-r--r--src/testing/test_exchange_management_api.c123
-rw-r--r--src/testing/test_exchange_p2p.c557
-rw-r--r--src/testing/test_kyc_api.c514
-rw-r--r--src/testing/test_kyc_api.conf189
-rw-r--r--src/testing/test_taler_exchange_aggregator.c263
-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/master.priv1
-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.c134
-rw-r--r--src/testing/testing_api_cmd_age_withdraw.c756
-rw-r--r--src/testing/testing_api_cmd_auditor_add.c81
-rw-r--r--src/testing/testing_api_cmd_auditor_add_denom_sig.c88
-rw-r--r--src/testing/testing_api_cmd_auditor_del.c69
-rw-r--r--src/testing/testing_api_cmd_auditor_deposit_confirmation.c158
-rw-r--r--src/testing/testing_api_cmd_auditor_exchanges.c380
-rw-r--r--src/testing/testing_api_cmd_auditor_exec_auditor.c1
-rw-r--r--src/testing/testing_api_cmd_bank_admin_add_incoming.c111
-rw-r--r--src/testing/testing_api_cmd_bank_admin_check.c29
-rw-r--r--src/testing/testing_api_cmd_bank_check.c86
-rw-r--r--src/testing/testing_api_cmd_bank_check_empty.c26
-rw-r--r--src/testing/testing_api_cmd_bank_history_credit.c531
-rw-r--r--src/testing/testing_api_cmd_bank_history_debit.c526
-rw-r--r--src/testing/testing_api_cmd_bank_transfer.c55
-rw-r--r--src/testing/testing_api_cmd_batch.c49
-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_change_auth.c136
-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.c251
-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.c314
-rw-r--r--src/testing/testing_api_cmd_deposits_get.c78
-rw-r--r--src/testing/testing_api_cmd_exec_aggregator.c1
-rw-r--r--src/testing/testing_api_cmd_exec_closer.c16
-rw-r--r--src/testing/testing_api_cmd_exec_expire.c162
-rw-r--r--src/testing/testing_api_cmd_exec_router.c161
-rw-r--r--src/testing/testing_api_cmd_exec_transfer.c1
-rw-r--r--src/testing/testing_api_cmd_exec_wget.c158
-rw-r--r--src/testing/testing_api_cmd_exec_wirewatch.c29
-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.c233
-rw-r--r--src/testing/testing_api_cmd_kyc_check_get.c65
-rw-r--r--src/testing/testing_api_cmd_kyc_proof.c78
-rw-r--r--src/testing/testing_api_cmd_kyc_wallet_get.c82
-rw-r--r--src/testing/testing_api_cmd_oauth.c52
-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_wire_fees.c (renamed from src/testing/testing_api_cmd_offline_sign_fees.c)2
-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.c166
-rw-r--r--src/testing/testing_api_cmd_recoup_refresh.c289
-rw-r--r--src/testing/testing_api_cmd_refresh.c553
-rw-r--r--src/testing/testing_api_cmd_refund.c167
-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.c2
-rw-r--r--src/testing/testing_api_cmd_revoke_denom_key.c54
-rw-r--r--src/testing/testing_api_cmd_revoke_sign_key.c56
-rw-r--r--src/testing/testing_api_cmd_rewind.c197
-rw-r--r--src/testing/testing_api_cmd_run_fakebank.c214
-rw-r--r--src/testing/testing_api_cmd_serialize_keys.c293
-rw-r--r--src/testing/testing_api_cmd_set_officer.c301
-rw-r--r--src/testing/testing_api_cmd_set_wire_fee.c75
-rw-r--r--src/testing/testing_api_cmd_stat.c64
-rw-r--r--src/testing/testing_api_cmd_status.c410
-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.c264
-rw-r--r--src/testing/testing_api_cmd_twister_exec_client.c2
-rw-r--r--src/testing/testing_api_cmd_wire.c117
-rw-r--r--src/testing/testing_api_cmd_wire_add.c81
-rw-r--r--src/testing/testing_api_cmd_wire_del.c63
-rw-r--r--src/testing/testing_api_cmd_withdraw.c316
-rw-r--r--src/testing/testing_api_helpers_auditor.c231
-rw-r--r--src/testing/testing_api_helpers_bank.c687
-rw-r--r--src/testing/testing_api_helpers_exchange.c974
-rw-r--r--src/testing/testing_api_loop.c929
-rw-r--r--src/testing/testing_api_misc.c394
-rw-r--r--src/testing/testing_api_traits.c57
-rw-r--r--src/testing/testing_api_twister_helpers.c16
-rw-r--r--src/testing/valgrind.h7165
-rw-r--r--src/util/.gitignore4
-rw-r--r--src/util/Makefile.am61
-rw-r--r--src/util/age_restriction.c795
-rw-r--r--src/util/aml_signatures.c201
-rw-r--r--src/util/amount.c83
-rw-r--r--src/util/auditor_signatures.c119
-rw-r--r--src/util/bench_age_restriction.c208
-rw-r--r--src/util/config.c446
-rw-r--r--src/util/conversion.c405
-rw-r--r--src/util/crypto.c321
-rw-r--r--src/util/crypto_confirmation.c293
-rw-r--r--src/util/crypto_contract.c661
-rw-r--r--src/util/crypto_helper_cs.c1316
-rw-r--r--src/util/crypto_helper_esign.c21
-rw-r--r--src/util/crypto_helper_rsa.c371
-rw-r--r--src/util/crypto_wire.c86
-rw-r--r--src/util/currencies.conf89
-rw-r--r--src/util/denom.c634
-rwxr-xr-xsrc/util/do_bench_age_restriction8
-rw-r--r--src/util/exchange_signatures.c1849
-rw-r--r--src/util/extension_age_restriction.c182
-rw-r--r--src/util/iban.c6
-rw-r--r--src/util/merchant_signatures.c278
-rw-r--r--src/util/offline_signatures.c956
-rw-r--r--src/util/paths.conf6
-rw-r--r--src/util/payto.c497
-rw-r--r--src/util/secmod_common.c44
-rw-r--r--src/util/secmod_common.h12
-rw-r--r--src/util/secmod_signatures.c125
-rw-r--r--src/util/taler-config.in4
-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.c72
-rw-r--r--src/util/taler-exchange-secmod-eddsa.conf8
-rw-r--r--src/util/taler-exchange-secmod-rsa.c769
-rw-r--r--src/util/taler-exchange-secmod-rsa.conf13
-rw-r--r--src/util/taler-exchange-secmod-rsa.h31
-rw-r--r--src/util/test_age_restriction.c442
-rw-r--r--src/util/test_amount.c17
-rw-r--r--src/util/test_conversion.c149
-rwxr-xr-xsrc/util/test_conversion.sh5
-rw-r--r--src/util/test_crypto.c361
-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.c2
-rw-r--r--src/util/test_helper_rsa.c405
-rw-r--r--src/util/test_helper_rsa.conf1
-rw-r--r--src/util/test_payto.c64
-rw-r--r--src/util/tv_age_restriction.c271
-rw-r--r--src/util/tv_age_restriction.json9764
-rw-r--r--src/util/url.c135
-rw-r--r--src/util/util.c416
-rw-r--r--src/util/wallet_signatures.c1618
1552 files changed, 237531 insertions, 80123 deletions
diff --git a/.gitignore b/.gitignore
index dbc9f07ab..bda987c5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,8 @@
*.gcno
*.gcda
*.mo
+*.blg
+*.bbl
.dirstamp
m4/*.m4
doc/coverage/
@@ -33,8 +35,10 @@ GPATH
GRTAGS
GTAGS
*.swp
+.DS_Store
src/include/taler_error_codes.h
-src/lib/test_exchange_api
+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
@@ -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/
@@ -82,6 +89,7 @@ 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
@@ -110,9 +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
@@ -120,9 +130,17 @@ 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.*
@@ -145,3 +163,13 @@ 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 416977559..01c07d992 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,10 +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/git/gana.git
+ 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/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 690686a02..f2232f498 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+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
diff --git a/Makefile.am b/Makefile.am
index 0bfe4ee05..83b761a59 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -15,11 +15,6 @@ else
endif
endif
-BUILT_SOURCES = src/include/taler_error_codes.h
-
-src/include/taler_error_codes.h: contrib/gana/gnu-taler-error-codes/registry.rec
- contrib/gana-update.sh
-
@DX_RULES@
ACLOCAL_AMFLAGS = -I m4
diff --git a/README b/README
index 1dbd9d614..7b092e0f1 100644
--- a/README
+++ b/README
@@ -13,18 +13,17 @@ 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
@@ -43,9 +42,9 @@ Dependencies:
These are the direct dependencies for running a Taler exchange:
-- GNUnet >= 0.15.4
+- GNUnet >= 0.21.1
- GNU libmicrohttpd >= 0.9.71
-- Postgres >= 9.5
+- PostgreSQL >= 15.0
@@ -63,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
@@ -71,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/
-
-2) Create the exchange's master with the tool 'gnunet-ecc':
+src/bank-lib/
+ -- bank REST client logic and implementation of an in-memory
+ RTGS emulator ("fakebank") for testing.
-gnunet-ecc -g1 master.priv
+src/extensions/
+ -- extensions to the core logic of an exchange
-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/json/
+ -- helper functions for generating and parsing JSON
-gnunet-ecc -p master.priv
+src/mhd/
+ -- helper functions for interacting with GNU libmicrohttpd
-4) Generate other exchange related keys ('denomination' and 'signing' keys), by issuing:
+src/curl/
+ -- helper functions for interacting with libcurl
-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/bootstrap b/bootstrap
index 65b71c7d8..509c8a0c7 100755
--- a/bootstrap
+++ b/bootstrap
@@ -1,4 +1,5 @@
#!/bin/sh
+# This file is in the public domain.
set -eu
@@ -7,11 +8,11 @@ if ! git --version >/dev/null; then
exit 1
fi
-
echo "$0: Updating submodules"
-echo | git submodule update --init
+echo | git submodule update --init --force --remote
-./contrib/gana.sh
+# 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:
@@ -31,4 +32,17 @@ 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 6995b9b3e..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-2021 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,7 +17,7 @@
#
#
AC_PREREQ([2.69])
-AC_INIT([taler-exchange], [0.8.5], [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])
@@ -33,8 +33,6 @@ AM_SILENT_RULES([yes])
AC_CONFIG_MACRO_DIR([m4])
AC_PROG_AWK
AC_PROG_CC
-# FIXME: AC_PROG_CC_C99 is obsolete, remove for autoconf 2.70
-AC_PROG_CC_C99
AC_PROG_OBJC
AC_PROG_INSTALL
AC_PROG_LN_S
@@ -65,6 +63,13 @@ AS_IF([test "x$doc_only" != xyes],[
# Force some CFLAGS
CFLAGS="-Wall -Wno-address-of-packed-member $CFLAGS"
+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)
+
+
# 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])
@@ -78,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],
@@ -122,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])
@@ -135,20 +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
- #include <gnunet/gnunet_common.h>
- #if GNUNET_UTIL_VERSION < 0x00A0104
- #fail libgnunetutil is too old
- #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 > 0.14.0 to build this program.
-*** (Yes, ">", libgnunetutil 0.14.0 is NOT enough.)
+*** You need libgnunetutil >= 0.21.1 to build this program.
*** This library is part of GNUnet, available at
*** https://gnunet.org
*** ]])])
@@ -167,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([[
***
@@ -185,14 +216,12 @@ 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
@@ -203,22 +232,14 @@ AS_IF([test "x$curl" = x1],[
])
-# 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([FATAL: No libgnurl/libcurl])])])
+# 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
@@ -233,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])
@@ -264,92 +297,29 @@ 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!)
*** ]])])
+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)],
- [], [#ifdef HAVE_GNUNET_PLATFORM_H
- #include <gnunet/platform.h>
- #endif])
+ [AC_CHECK_LIB([gnunetsq], [GNUNET_SQ_result_spec_string], libgnunetsq=1)])
-# 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"
-
-
-AM_CONDITIONAL(HAVE_POSTGRESQL, test x$postgres = xtrue)
CFLAGS=$CFLAGS_SAVE
LDFLAGS=$LDFLAGS_SAVE
@@ -399,10 +369,7 @@ AS_CASE([$with_twister],
CPPFLAGS="-I$with_twister/include $CPPFLAGS"])
AC_CHECK_HEADERS([taler/taler_twister_service.h],
- [AC_CHECK_LIB([talertwister], [TALER_TWISTER_connect], talertwister=1)],
- [], [#ifdef HAVE_GNUNET_PLATFORM_H
- #include <gnunet/platform.h>
- #endif])
+ [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)?
@@ -419,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:
@@ -472,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"],
[
@@ -519,7 +491,6 @@ 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])
@@ -541,7 +512,10 @@ 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
@@ -549,6 +523,7 @@ AC_CONFIG_FILES([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 0d42e1df8..000000000
--- a/contrib/Makefile.am
+++ /dev/null
@@ -1,70 +0,0 @@
-SUBDIRS = .
-
-# English (en)
-tosendir=$(datadir)/taler/exchange/tos/en
-
-# English (en)
-ppendir=$(datadir)/taler/exchange/pp/en
-
-rdatadir=$(datadir)/taler/exchange
-
-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-nexus-prepare
-
-EXTRA_DIST = \
- $(bin_SCRIPTS) \
- $(tosen_DATA) \
- $(ppen_DATA) \
- update-tos.sh \
- update-pp.sh \
- gana-update.sh \
- gana/gnu-taler-error-codes/registry.rec \
- gana/gnu-taler-error-codes/Makefile \
- 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 38f44d4e9..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,51 +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 }} &
-{% if 'claimed_done' in item %}
- {{ item.claimed_done }}
+ {{ 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 %}
- N/A
-{% endif %} \\
-\nopagebreak
- \multicolumn{4}{l}{ {\tt \small {{ item.coin_pub }} } } \\
+ \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 %}
@@ -304,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
@@ -599,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
@@ -768,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
@@ -887,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.
@@ -897,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}
@@ -912,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 %}
@@ -928,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
@@ -974,10 +1043,10 @@ with respect to outgoing wire transfers.
\subsection{Denominations without auditor signature}
This section highlights denomination keys that lack a proper
-signature from the {\t taler-auditor-offline} tool. This may be
+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 denomnations. So this must be read with a keen eye on the
+future denominations. So this must be read with a keen eye on the
business situation.
@@ -1093,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 e6d003d88..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 as required by law and local regulations
- * 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
diff --git a/contrib/exchange-tos-bfh-v0.rst b/contrib/exchange-tos-bfh-v0.rst
new file mode 100644
index 000000000..33a2b76f7
--- /dev/null
+++ b/contrib/exchange-tos-bfh-v0.rst
@@ -0,0 +1,307 @@
+Terms of Service
+================
+
+Last update: 26.04.2024
+
+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
+--------
+
+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 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.
+ 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
+ follow responsible disclosure if you find security issues in our services.
+* 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.
+
+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
+ice@bfh.ch. 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 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 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 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
+----
+
+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 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.
+
+
+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 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.
+
+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/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/tos/tos.rst b/contrib/exchange-tos-v0.rst
index 2509b87b1..1fdb66664 100644
--- a/contrib/tos/tos.rst
+++ b/contrib/exchange-tos-v0.rst
@@ -1,7 +1,8 @@
-Terms Of Service
+Terms of Service
================
-Last Updated: 12.4.2019
+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
@@ -20,27 +21,17 @@ 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.
+-----------
+
+* 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
@@ -69,12 +60,12 @@ 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
+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
+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
@@ -96,7 +87,6 @@ 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
----
@@ -107,7 +97,7 @@ 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
+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
@@ -137,13 +127,12 @@ The Taler Wallet is released under the terms of the GNU General Public License
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
+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 using the Taler protocol, assuming your use is compatible with an
+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
--------------------------------------------------
@@ -154,22 +143,19 @@ 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,
+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.
+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,
@@ -178,19 +164,16 @@ 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:
-
- (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
+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:
-(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
@@ -221,10 +204,11 @@ 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.
+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,
@@ -257,7 +241,6 @@ 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
---------------------------------------------------
@@ -288,7 +271,6 @@ 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/gana b/contrib/gana
-Subproject b0dd85e8187f33a1f92dd5eb31082050d333e16
+Subproject cc2d9bdb3e90af5ddebf964e3da492c04a30741
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.sh b/contrib/gana-latest.sh
index 30dc7994e..e92761acc 100755
--- a/contrib/gana.sh
+++ b/contrib/gana-latest.sh
@@ -7,4 +7,4 @@ cd contrib/gana
git pull origin master
cd ../..
-exec ./contrib/gana-update.sh
+exec ./contrib/gana-generate.sh
diff --git a/contrib/gana-update.sh b/contrib/gana-update.sh
deleted file mode 100755
index 8fcf18852..000000000
--- a/contrib/gana-update.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/sh
-# Helper script to recompute error codes based on submodule
-# Run from exchange/ main directory.
-set -eu
-
-# Generate taler-error-codes.h in gana and copy it to
-# src/include/taler_error_codes.h
-cd contrib/gana/gnu-taler-error-codes
-make
-cd ../../..
-for n in taler_error_codes.c
-do
- if ! diff contrib/gana/gnu-taler-error-codes/${n} src/util/${n} > /dev/null
- then
- cp contrib/gana/gnu-taler-error-codes/$n src/util/$n
- fi
-done
-for n in taler_error_codes.h
-do
- if ! diff contrib/gana/gnu-taler-error-codes/${n} src/include/${n} > /dev/null
- then
- cp contrib/gana/gnu-taler-error-codes/$n src/include/$n
- fi
-done
-cd contrib/gana/gnu-taler-db-events
-make
-cd ../../..
-for n in taler_dbevents.h
-do
- if ! diff contrib/gana/gnu-taler-db-events/${n} src/include/${n} > /dev/null
- then
- cp contrib/gana/gnu-taler-db-events/$n src/include/$n
- fi
-done
diff --git a/contrib/gnunet.tag b/contrib/gnunet.tag
index f270ba5ea..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>
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 831e1c4e4..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>
@@ -172,6 +190,30 @@
<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/debian/etc-taler-exchange/taler/secrets/exchange-accountcredentials.secret.conf b/contrib/packages/fedora/etc-taler-exchange/taler/secrets/exchange-accountcredentials-1.secret.conf
index 8c8d14320..8c8d14320 100644
--- a/debian/etc-taler-exchange/taler/secrets/exchange-accountcredentials.secret.conf
+++ b/contrib/packages/fedora/etc-taler-exchange/taler/secrets/exchange-accountcredentials-1.secret.conf
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/Makefile b/contrib/pp/Makefile
deleted file mode 100644
index ab29543cb..000000000
--- a/contrib/pp/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/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 7e9e5343c..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 ce7045350..000000000
--- a/contrib/pp/en/0.html
+++ /dev/null
@@ -1,211 +0,0 @@
-
-
-<html lang="en">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Privacy Policy &#8212; Taler Privacy Policy</title>
- <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
- <link rel="stylesheet" href="_static/epub.css" type="text/css" />
- <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
- <script src="_static/jquery.js"></script>
- <script src="_static/underscore.js"></script>
- <script src="_static/doctools.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><p>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.</p></li>
-<li><p>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.</p></li>
-<li><p>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.</p></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><p>to transfer money as specified by our users (Taler transactions);</p></li>
-<li><p>to assist government entities in linking income to the underlying contract as required by law and local regulations</p></li>
-<li><p>to support you using the Taler Wallet or to improve our Services</p></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.</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="what-are-your-data-protection-rights">
-<h2>What are your data protection rights?<a class="headerlink" href="#what-are-your-data-protection-rights" title="Permalink to this headline">¶</a></h2>
-<p>Anastasis would like to make sure you are fully aware of all of your
-data protection rights. Every user is entitled to the following:</p>
-<dl class="simple">
-<dt><strong>The right to access</strong>: You have the right to request Anastasis for</dt><dd><p>copies of your personal data. We may charge you a small fee for this
-service.</p>
-</dd>
-</dl>
-<p><strong>The right to rectification</strong>: 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.</p>
-<dl class="simple">
-<dt><strong>The right to restrict processing</strong>: You have the right to request</dt><dd><p>that Anastasis restrict the processing of your personal data, under
-certain conditions.</p>
-</dd>
-<dt><strong>The right to object to processing</strong>: You have the right to object to</dt><dd><p>Anastasis’s processing of your personal data, under certain
-conditions.</p>
-</dd>
-<dt><strong>The right to data portability</strong>: You have the right to request that</dt><dd><p>Anastasis transfer the data that we have collected to another
-organization, or directly to you, under certain conditions.</p>
-</dd>
-</dl>
-<p>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: <a class="reference external" href="mailto:privacy&#37;&#52;&#48;taler-systems&#46;com">privacy<span>&#64;</span>taler-systems<span>&#46;</span>com</a></p>
-<p>You can always contact your local data protection authority to enforce
-your rights.</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 class="clearer"></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 842a83066..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 d6e42faf6..000000000
--- a/contrib/pp/en/0.txt
+++ /dev/null
@@ -1,237 +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 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
-==============================================
-
-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.
-
-
-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.
-
-
-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
-==============
-
-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 d4400542c..000000000
--- a/contrib/pp/en/0.xml
+++ /dev/null
@@ -1,214 +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.16 -->
-<document source="/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 as required by law and local regulations</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.</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="what-are-your-data-protection-rights" names="what\ are\ your\ data\ protection\ rights?">
- <title>What are your data protection rights?</title>
- <paragraph>Anastasis would like to make sure you are fully aware of all of your
- data protection rights. Every user is entitled to the following:</paragraph>
- <definition_list>
- <definition_list_item>
- <term><strong>The right to access</strong>: You have the right to request Anastasis for</term>
- <definition>
- <paragraph>copies of your personal data. We may charge you a small fee for this
- service.</paragraph>
- </definition>
- </definition_list_item>
- </definition_list>
- <paragraph><strong>The right to rectification</strong>: 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.</paragraph>
- <definition_list>
- <definition_list_item>
- <term><strong>The right to restrict processing</strong>: You have the right to request</term>
- <definition>
- <paragraph>that Anastasis restrict the processing of your personal data, under
- certain conditions.</paragraph>
- </definition>
- </definition_list_item>
- <definition_list_item>
- <term><strong>The right to object to processing</strong>: You have the right to object to</term>
- <definition>
- <paragraph>Anastasis’s processing of your personal data, under certain
- conditions.</paragraph>
- </definition>
- </definition_list_item>
- <definition_list_item>
- <term><strong>The right to data portability</strong>: You have the right to request that</term>
- <definition>
- <paragraph>Anastasis transfer the data that we have collected to another
- organization, or directly to you, under certain conditions.</paragraph>
- </definition>
- </definition_list_item>
- </definition_list>
- <paragraph>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: <reference refuri="mailto:privacy@taler-systems.com">privacy@taler-systems.com</reference></paragraph>
- <paragraph>You can always contact your local data protection authority to enforce
- your rights.</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 b97b70372..000000000
--- a/contrib/pp/locale/de/LC_MESSAGES/pp.po
+++ /dev/null
@@ -1,221 +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>, YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: pp 0\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-09-30 21:41+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"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\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 as required by law and local regulations"
-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."
-msgstr ""
-
-#: ../../pp.rst:96
-msgid "Protection of us and others"
-msgstr ""
-
-#: ../../pp.rst:98
-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:104
-msgid "What personal information can I access or change?"
-msgstr ""
-
-#: ../../pp.rst:106
-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:119
-msgid "What are your data protection rights?"
-msgstr ""
-
-#: ../../pp.rst:121
-msgid "Anastasis would like to make sure you are fully aware of all of your data protection rights. Every user is entitled to the following:"
-msgstr ""
-
-#: ../../pp.rst:126
-msgid "**The right to access**: You have the right to request Anastasis for"
-msgstr ""
-
-#: ../../pp.rst:125
-msgid "copies of your personal data. We may charge you a small fee for this service."
-msgstr ""
-
-#: ../../pp.rst:128
-msgid "**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."
-msgstr ""
-
-#: ../../pp.rst:137
-msgid "**The right to restrict processing**: You have the right to request"
-msgstr ""
-
-#: ../../pp.rst:136
-msgid "that Anastasis restrict the processing of your personal data, under certain conditions."
-msgstr ""
-
-#: ../../pp.rst:141
-msgid "**The right to object to processing**: You have the right to object to"
-msgstr ""
-
-#: ../../pp.rst:140
-msgid "Anastasis's processing of your personal data, under certain conditions."
-msgstr ""
-
-#: ../../pp.rst:145
-msgid "**The right to data portability**: You have the right to request that"
-msgstr ""
-
-#: ../../pp.rst:144
-msgid "Anastasis transfer the data that we have collected to another organization, or directly to you, under certain conditions."
-msgstr ""
-
-#: ../../pp.rst:147
-msgid "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"
-msgstr ""
-
-#: ../../pp.rst:151
-msgid "You can always contact your local data protection authority to enforce your rights."
-msgstr ""
-
-#: ../../pp.rst:156
-msgid "Data retention"
-msgstr ""
-
-#: ../../pp.rst:158
-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:165
-msgid "Data security"
-msgstr ""
-
-#: ../../pp.rst:167
-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:180
-msgid "Changes and updates to privacy policy"
-msgstr ""
-
-#: ../../pp.rst:182
-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:198
-msgid "International users and visitors"
-msgstr ""
-
-#: ../../pp.rst:200
-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:209
-msgid "Questions"
-msgstr ""
-
-#: ../../pp.rst:211
-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 4fa80b177..000000000
--- a/contrib/taler-bank-manage-testing
+++ /dev/null
@@ -1,27 +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
-
-taler-bank-manage -c $1 --with-db $2 django changepassword_unsafe Exchange x
-
-# 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-nexus-prepare b/contrib/taler-nexus-prepare
deleted file mode 100755
index 66a0b1c1e..000000000
--- a/contrib/taler-nexus-prepare
+++ /dev/null
@@ -1,128 +0,0 @@
-#!/usr/bin/env python3
-# This file is in the public domain.
-
-from requests import get, post
-from subprocess import call
-import base64
-
-# EBICS details
-EBICS_URL = "http://localhost:5000/ebicsweb"
-HOST_ID = "HOST01"
-PARTNER_ID = "PARTNER1"
-USER_ID = "USER1"
-EBICS_VERSION = "H004"
-
-SUBSCRIBER_IBAN = "ES9121000418450200051332"
-SUBSCRIBER_BIC = "BIC"
-SUBSCRIBER_NAME = "Exchange"
-
-BANK_ACCOUNT_LABEL = "my-bank-account"
-BANK_CONNECTION_LABEL = "my-bank-connection"
-FACADE_LABEL="my-facade"
-
-USERNAME="Exchange"
-USER_AUTHORIZATION_HEADER = "basic {}".format(
- base64.b64encode(b"Exchange:x").decode("utf-8")
-)
-
-def assertResponse(response):
- if response.status_code != 200:
- print("Test failed on URL: {}".format(response.url))
- # stdout/stderr from both services is A LOT of text.
- # Confusing to dump all that to console.
- print("Check nexus.log and sandbox.log, probably under /tmp")
- exit(1)
- # Allows for finer grained checks.
- return response
-
-# Create a nexus (super-) user
-check_call(["libeufin-nexus",
- "superuser",
- "--db-name", "/tmp/nexus-exchange-test.sqlite3",
- "Exchange",
- "--password", "x"]
-)
-
-# Create a EBICS bank connection.
-assertResponse(
- post(
- "http://localhost:5001/bank-connections",
- json=dict(
- name=BANK_CONNECTION_LABEL,
- source="new",
- type="ebics",
- data=dict(
- ebicsURL=EBICS_URL, hostID=HOST_ID, partnerID=PARTNER_ID, userID=USER_ID
- ),
- ),
- headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
- )
-)
-
-# Create a facade
-assertResponse(
- post(
- "http://localhost:5001/facades",
- json=dict(
- name=FACADE_LABEL,
- type="taler-wire-gateway",
- creator=USERNAME,
- config=dict(
- bankAccount=BANK_ACCOUNT_LABEL,
- bankConnection=BANK_CONNECTION_LABEL,
- reserveTransferLevel="UNUSED",
- intervalIncremental="UNUSED"
- )
- ),
- headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
- )
-)
-
-# Create the EBICS host at the Sandbox.
-assertResponse(
- post(
- "http://localhost:5000/admin/ebics/host",
- json=dict(hostID=HOST_ID, ebicsVersion=EBICS_VERSION),
- )
-)
-
-# Create Exchange EBICS subscriber at the Sandbox.
-assertResponse(
- post(
- "http://localhost:5000/admin/ebics/subscribers",
- json=dict(hostID=HOST_ID, partnerID=PARTNER_ID, userID=USER_ID),
- )
-)
-
-# Create a bank account associated to the Exchange's EBICS subscriber,
-# again at the Sandbox.
-assertResponse(
- post(
- "http://localhost:5000/admin/ebics/bank-accounts",
- json=dict(
- subscriber=dict(hostID=HOST_ID, partnerID=PARTNER_ID, userID=USER_ID),
- iban=SUBSCRIBER_IBAN,
- bic=SUBSCRIBER_BIC,
- name=SUBSCRIBER_NAME,
- label=BANK_ACCOUNT_LABEL,
- ),
- )
-)
-
-# 'connect' to the bank: upload+download keys.
-assertResponse(
- post(
- "http://localhost:5001/bank-connections/{}/connect".format(BANK_CONNECTION_LABEL),
- json=dict(),
- headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
- )
-)
-
-# Download bank accounts.
-assertResponse(
- post(
- "http://localhost:5001/bank-connections/{}/ebics/import-accounts".format(BANK_CONNECTION_LABEL),
- json=dict(),
- headers=dict(Authorization=USER_AUTHORIZATION_HEADER),
- )
-)
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 2e6c66093..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 6e7f66f52..000000000
--- a/contrib/tos/en/0.html
+++ /dev/null
@@ -1,304 +0,0 @@
-
-
-<html lang="en">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Terms Of Service &#8212; Taler Terms of Service</title>
- <link rel="stylesheet" href="_static/pygments.css" type="text/css" />
- <link rel="stylesheet" href="_static/epub.css" type="text/css" />
- <script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
- <script src="_static/jquery.js"></script>
- <script src="_static/underscore.js"></script>
- <script src="_static/doctools.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><p>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.</p></li>
-<li><p>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.</p></li>
-<li><p>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.</p></li>
-<li><p>You agree to not intentionally overwhelm our systems with requests and
-follow responsible disclosure if you find security issues in our services.</p></li>
-<li><p>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.</p></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>
-<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="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-and-financial-self-responsibility">
-<h2>Eligibility and Financial self-responsibility<a class="headerlink" href="#eligibility-and-financial-self-responsibility" 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>
-<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="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><p>user error such as forgotten passwords, incorrectly constructed
-transactions;</p></li>
-<li><p>server failure or data loss;</p></li>
-<li><p>unauthorized access to the Taler Wallet application;</p></li>
-<li><p>bugs or other errors in the Taler Wallet software; and</p></li>
-<li><p>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.</p></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>
-<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><p>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:</p></li>
-</ol>
-<blockquote>
-<div><ol class="lowerroman simple">
-<li><p>your use of, or conduct in connection with, our services;</p></li>
-<li><p>any unauthorized use of your wallet and/or private key due to your
-failure to maintain the confidentiality of your wallet;</p></li>
-<li><p>any interruption or cessation of transmission to or from the services; or</p></li>
-<li><p>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</p></li>
-</ol>
-</div></blockquote>
-<ol class="loweralpha simple" start="2">
-<li><p>any direct damages.</p></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>
-<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-and-time-limitation-on-claims-and-termination">
-<h2>Indemnity and Time limitation on claims and Termination<a class="headerlink" href="#indemnity-and-time-limitation-on-claims-and-termination" 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>
-<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>
-<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-and-force-majeure">
-<h2>Discontinuance of services and Force majeure<a class="headerlink" href="#discontinuance-of-services-and-force-majeure" 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>
-<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="governing-law-waivers-severability-and-assignment">
-<h2>Governing law, Waivers, Severability and Assignment<a class="headerlink" href="#governing-law-waivers-severability-and-assignment" 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>
-<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>
-<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>
-<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>
-<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 class="clearer"></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 89d394939..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 c124d70ee..000000000
--- a/contrib/tos/en/0.txt
+++ /dev/null
@@ -1,337 +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.
-
-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 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.
-
-
-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 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.
-
-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/tos/en/0.xml b/contrib/tos/en/0.xml
deleted file mode 100644
index ee93a4cc1..000000000
--- a/contrib/tos/en/0.xml
+++ /dev/null
@@ -1,311 +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.16 -->
-<document source="/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>
- <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="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-and-financial-self-responsibility" names="eligibility\ and\ financial\ self-responsibility">
- <title>Eligibility and Financial self-responsibility</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>
- <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="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>
- <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>
- <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-and-time-limitation-on-claims-and-termination" names="indemnity\ and\ time\ limitation\ on\ claims\ and\ termination">
- <title>Indemnity and Time limitation on claims and Termination</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>
- <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>
- <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-and-force-majeure" names="discontinuance\ of\ services\ and\ force\ majeure">
- <title>Discontinuance of services and Force majeure</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>
- <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="governing-law-waivers-severability-and-assignment" names="governing\ law,\ waivers,\ severability\ and\ assignment">
- <title>Governing law, Waivers, Severability and Assignment</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>
- <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>
- <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>
- <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>
- <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 e821c9c1f..000000000
--- a/contrib/tos/locale/de/LC_MESSAGES/tos.po
+++ /dev/null
@@ -1,241 +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>, YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: tos 0\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-09-30 21:42+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"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\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:90
-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:101
-msgid "Fees"
-msgstr ""
-
-#: ../../tos.rst:103
-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:118
-msgid "Eligibility and Financial self-responsibility"
-msgstr ""
-
-#: ../../tos.rst:120
-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:125
-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:133
-msgid "Copyrights and trademarks"
-msgstr ""
-
-#: ../../tos.rst:135
-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:148
-msgid "Limitation of liability & disclaimer of warranties"
-msgstr ""
-
-#: ../../tos.rst:150
-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:160
-msgid "user error such as forgotten passwords, incorrectly constructed transactions;"
-msgstr ""
-
-#: ../../tos.rst:162
-msgid "server failure or data loss;"
-msgstr ""
-
-#: ../../tos.rst:163
-msgid "unauthorized access to the Taler Wallet application;"
-msgstr ""
-
-#: ../../tos.rst:164
-msgid "bugs or other errors in the Taler Wallet software; and"
-msgstr ""
-
-#: ../../tos.rst:165
-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:170
-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:174
-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:181
-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:185
-msgid "your use of, or conduct in connection with, our services;"
-msgstr ""
-
-#: ../../tos.rst:186
-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:188
-msgid "any interruption or cessation of transmission to or from the services; or"
-msgstr ""
-
-#: ../../tos.rst:189
-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:193
-msgid "any direct damages."
-msgstr ""
-
-#: ../../tos.rst:195
-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:202
-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:219
-msgid "Indemnity and Time limitation on claims and Termination"
-msgstr ""
-
-#: ../../tos.rst:221
-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:229
-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:233
-msgid "In the event of termination concerning your use of our Services, your obligations under this Agreement will still continue."
-msgstr ""
-
-#: ../../tos.rst:238
-msgid "Discontinuance of services and Force majeure"
-msgstr ""
-
-#: ../../tos.rst:240
-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:250
-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:262
-msgid "Governing law, Waivers, Severability and Assignment"
-msgstr ""
-
-#: ../../tos.rst:264
-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:272
-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:277
-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:280
-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:284
-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:293
-msgid "Questions or comments"
-msgstr ""
-
-#: ../../tos.rst:295
-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/uncrustify_precommit b/contrib/uncrustify_precommit
index 24873330f..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 "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 68b2ef735377373da29fd20aeb9facaf91c0383
diff --git a/debian/changelog b/debian/changelog
index 8dcaf12d4..cab5345dc 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,151 @@
+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.
diff --git a/debian/control b/debian/control
index 894a1dd37..cf99dd1ed 100644
--- a/debian/control
+++ b/debian/control
@@ -7,29 +7,23 @@ Build-Depends:
automake (>=1.11.1),
autopoint,
bash,
+ gcc-12,
debhelper-compat (= 12),
gettext,
- libgnunet-dev (>=0.15.3),
+ libgnunet-dev (>=0.21),
libcurl4-gnutls-dev (>=7.35.0) | libcurl4-openssl-dev (>= 7.35.0),
- libgcrypt20-dev (>=1.6),
+ libgcrypt20-dev (>=1.8),
libgnutls28-dev (>=3.2.12),
libidn2-dev,
- libjansson-dev,
+ libjansson-dev (>= 2.13),
libltdl-dev (>=2.2),
- libmicrohttpd-dev (>=0.9.63),
- libpq-dev (>=9.5),
+ libmicrohttpd-dev (>=0.9.71),
+ libpq-dev (>=14),
libsodium-dev (>=1.0.11),
- libsqlite3-dev (>=3.8),
libunistring-dev (>=0.9.2),
- python3-jinja2,
- net-tools,
po-debconf,
- python3-dev,
texinfo (>=5.2),
zlib1g-dev
-Build-Conflicts:
- autoconf2.13,
- automake1.4
Standards-Version: 4.5.0
Vcs-Git: https://salsa.debian.org/debian/taler-exchange.git
Vcs-browser: https://salsa.debian.org/debian/taler-exchange
@@ -44,7 +38,26 @@ Depends:
netbase,
${misc:Depends},
${shlibs:Depends}
-Description: libraries to talk to a GNU Taler exchange
+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
@@ -55,7 +68,10 @@ Depends:
netbase,
${misc:Depends},
${shlibs:Depends}
-Description: programs and libraries to manage a GNU Taler exchange database
+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
@@ -68,12 +84,29 @@ Depends:
lsb-base,
netbase,
ucf,
- dbconfig-pgsql | dbconfig-no-thanks,
${misc:Depends},
${shlibs:Depends}
Recommends:
- taler-exchange-offline (= ${binary:Version})
-Description: GNU's payment system operator
+ 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
@@ -86,7 +119,14 @@ Depends:
netbase,
${misc:Depends},
${shlibs:Depends}
-Description: tools for managing the GNU Taler exchange offline keys
+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
@@ -98,20 +138,26 @@ Depends:
adduser,
lsb-base,
netbase,
- dbconfig-pgsql | dbconfig-no-thanks,
- python3-jinja2,
${misc:Depends},
${shlibs:Depends}
-Description: GNU's payment system auditor
+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.15.3),
- libgcrypt20-dev (>=1.6),
- libmicrohttpd-dev (>=0.9.33),
+ 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)
diff --git a/debian/etc-libtalerexchange/taler/taler.conf b/debian/etc-libtalerexchange/taler/taler.conf
index 71663be5e..2cf815656 100644
--- a/debian/etc-libtalerexchange/taler/taler.conf
+++ b/debian/etc-libtalerexchange/taler/taler.conf
@@ -30,14 +30,16 @@
# 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
+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.
diff --git a/debian/etc-taler-auditor/nginx/sites-available/taler-auditor b/debian/etc-taler-auditor/nginx/sites-available/taler-auditor
index 3fdffdad3..f74035d53 100644
--- a/debian/etc-taler-auditor/nginx/sites-available/taler-auditor
+++ b/debian/etc-taler-auditor/nginx/sites-available/taler-auditor
@@ -1,7 +1,18 @@
-location /taler-auditor/ {
- proxy_pass http://unix:/var/lib/taler-auditor/auditor.sock;
- proxy_redirect off;
- proxy_set_header Host $host;
- proxy_set_header X-Forwarded-Host "example.com";
- proxy_set_header X-Forwarded-Proto "https";
+server {
+
+ listen 80;
+ listen [::]:80;
+
+ server_name localhost;
+
+ access_log /var/log/nginx/auditor.log;
+ error_log /var/log/nginx/auditor.err;
+
+ location /taler-auditor/ {
+ proxy_pass http://unix:/var/lib/taler-auditor/auditor.sock;
+ proxy_redirect off;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-Host "localhost";
+ #proxy_set_header X-Forwarded-Proto "https";
+ }
} \ No newline at end of file
diff --git a/debian/etc-taler-auditor/taler/secrets/auditor-db.secret.conf b/debian/etc-taler-auditor/taler/secrets/auditor-db.secret.conf
index b81bb817f..1278a563b 100644
--- a/debian/etc-taler-auditor/taler/secrets/auditor-db.secret.conf
+++ b/debian/etc-taler-auditor/taler/secrets/auditor-db.secret.conf
@@ -4,7 +4,7 @@
# Typically, there should only be a single line here, of the form:
-CONFIG=postgres:///DATABASE
+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/nginx/sites-available/taler-exchange b/debian/etc-taler-exchange/nginx/sites-available/taler-exchange
index 13ab88633..9b61a32df 100644
--- a/debian/etc-taler-exchange/nginx/sites-available/taler-exchange
+++ b/debian/etc-taler-exchange/nginx/sites-available/taler-exchange
@@ -2,13 +2,16 @@ server {
listen 80;
listen [::]:80;
- #server_name example.com;
+ server_name localhost;
+
+ access_log /var/log/nginx/exchange.log;
+ error_log /var/log/nginx/exchange.err;
location /taler-exchange/ {
proxy_pass http://unix:/run/taler/exchange-httpd/exchange-http.sock:/;
proxy_redirect off;
proxy_set_header Host $host;
- #proxy_set_header X-Forwarded-Host "example.com";
+ proxy_set_header X-Forwarded-Host "localhost";
#proxy_set_header X-Forwarded-Proto "https";
}
}
diff --git a/debian/etc-taler-exchange/taler/conf.d/exchange-business.conf b/debian/etc-taler-exchange/taler/conf.d/exchange-business.conf
index 554852a14..d5938f2b1 100644
--- a/debian/etc-taler-exchange/taler/conf.d/exchange-business.conf
+++ b/debian/etc-taler-exchange/taler/conf.d/exchange-business.conf
@@ -6,11 +6,19 @@
# which you can get using `taler-exchange-offline setup`.
# This is just an example, your key will be different!
# MASTER_PUBLIC_KEY = YE6Q6TR1EDB7FD0S68TGDZGF1P0GHJD2S0XVV8R2S62MYJ6HJ4ZG
-MASTER_PUBLIC_KEY =
+# MASTER_PUBLIC_KEY =
# Publicly visible base URL of the exchange.
# BASE_URL = https://example.com/
-BASE_URL =
+# BASE_URL =
+
+# 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
@@ -20,18 +28,23 @@ BASE_URL =
# TERMS_ETAG =
# PRIVACY_ETAG =
+SERVE = unix
+UNIXPATH_MODE = 666
# Bank accounts used by the exchange should be specified here:
[exchange-account-1]
-enable_credit = yes
-enable_debit = yes
+ENABLE_CREDIT = NO
+ENABLE_DEBIT = NO
# Account identifier in the form of an RFC-8905 payto:// URI.
# For SEPA, looks like payto://sepa/$IBAN?receiver-name=$NAME
# Make sure to URL-encode spaces in $NAME!
-payto_uri =
+PAYTO_URI =
# Credentials to access the account are in a separate
# config file with restricted permissions.
-@inline-secret@ exchange-accountcredentials-1 ../secrets/exchange-accountcredentials.secret.conf
+@inline-secret@ exchange-accountcredentials-1 ../secrets/exchange-accountcredentials-1.secret.conf
+
+
+
diff --git a/debian/etc-taler-exchange/taler/conf.d/exchange-system.conf b/debian/etc-taler-exchange/taler/conf.d/exchange-system.conf
index 75c670f71..4ad7e06f6 100644
--- a/debian/etc-taler-exchange/taler/conf.d/exchange-system.conf
+++ b/debian/etc-taler-exchange/taler/conf.d/exchange-system.conf
@@ -8,3 +8,6 @@
# 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
index 596dcc92b..08c20074c 100644
--- a/debian/etc-taler-exchange/taler/secrets/exchange-db.secret.conf
+++ b/debian/etc-taler-exchange/taler/secrets/exchange-db.secret.conf
@@ -4,7 +4,7 @@
# Typically, there should only be a single line here, of the form:
-CONFIG=postgres:///DATABASE
+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
index e21973509..aa1de818a 100644
--- a/debian/libtalerexchange-dev.install
+++ b/debian/libtalerexchange-dev.install
@@ -1,13 +1,20 @@
# 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-bank-benchmark
+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*
-# Only used in test cases. Maybe these
-# shouldn't even be installed?
-usr/bin/taler-nexus-prepare
-usr/bin/taler-bank-manage-testing
# Headers
usr/include/taler/*
@@ -22,5 +29,4 @@ usr/lib/*/libtalertesting.so
usr/lib/*/libtalerfakebank.so
# Documentation
-usr/share/man/man1/taler-exchange-benchmark*
usr/share/info/taler-developer-manual*
diff --git a/debian/libtalerexchange.install b/debian/libtalerexchange.install
index 35961e827..f3c52ba8d 100644
--- a/debian/libtalerexchange.install
+++ b/debian/libtalerexchange.install
@@ -2,6 +2,7 @@ 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
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/rules b/debian/rules
index 13d556472..3d8809c50 100755
--- a/debian/rules
+++ b/debian/rules
@@ -7,6 +7,9 @@ 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)
@@ -33,15 +36,20 @@ override_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
- dh_installsystemd -ptaler-exchange --name=taler-exchange-aggregator --no-start --no-enable
- dh_installsystemd -ptaler-exchange --name=taler-exchange-transfer --no-start --no-enable
- dh_installsystemd -ptaler-exchange --name=taler-exchange-wirewatch --no-start --no-enable
- dh_installsystemd -ptaler-exchange --name=taler-exchange-secmod-eddsa --no-start --no-enable
- dh_installsystemd -ptaler-exchange --name=taler-exchange-secmod-rsa --no-start --no-enable
- dh_installsystemd -ptaler-exchange --name=taler-exchange-closer --no-start --no-enable
- dh_installsystemd -ptaler-auditor --name=taler-auditor-httpd --no-start --no-enable
- dh_installsystemd -ptaler-exchange --name=taler-exchange --no-start --no-enable
+ 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
diff --git a/debian/taler-auditor.install b/debian/taler-auditor.install
index 0d7d941a0..4f3d5a1b2 100644
--- a/debian/taler-auditor.install
+++ b/debian/taler-auditor.install
@@ -1,16 +1,20 @@
usr/bin/taler-auditor
+usr/bin/taler-auditor-dbconfig
usr/bin/taler-auditor-dbinit
-usr/bin/taler-auditor-exchange
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/*
diff --git a/debian/taler-auditor.postinst b/debian/taler-auditor.postinst
index 4e89be226..847e4aac1 100644
--- a/debian/taler-auditor.postinst
+++ b/debian/taler-auditor.postinst
@@ -20,9 +20,10 @@ configure)
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
+ if ! dpkg-statoverride --list /etc/taler/secrets/auditor-db.secret.conf >/dev/null 2>&1
+ then
dpkg-statoverride --add --update \
- ${_USERNAME} ${_GROUPNAME} 660 \
+ ${_USERNAME} ${_GROUPNAME} 640 \
/etc/taler/secrets/auditor-db.secret.conf
fi
diff --git a/debian/taler-auditor.postrm b/debian/taler-auditor.postrm
index 752510e63..639e3241e 100644
--- a/debian/taler-auditor.postrm
+++ b/debian/taler-auditor.postrm
@@ -6,9 +6,16 @@ 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) ;;
*)
diff --git a/debian/taler-auditor.taler-auditor-httpd.service b/debian/taler-auditor.taler-auditor-httpd.service
index 9aefab641..ac68e41c8 100644
--- a/debian/taler-auditor.taler-auditor-httpd.service
+++ b/debian/taler-auditor.taler-auditor-httpd.service
@@ -6,7 +6,8 @@ After=postgres.service network.target
User=taler-auditor-httpd
Type=simple
Restart=on-failure
-ExecStart=/usr/bin/taler-auditor-httpd -c /etc/taler/taler.conf
+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-exchange-database.install b/debian/taler-exchange-database.install
index 56332366d..da8b0dc47 100644
--- a/debian/taler-exchange-database.install
+++ b/debian/taler-exchange-database.install
@@ -1,5 +1,7 @@
+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
diff --git a/debian/taler-exchange-offline.postinst b/debian/taler-exchange-offline.postinst
index e22ad5920..337bfa5d4 100644
--- a/debian/taler-exchange-offline.postinst
+++ b/debian/taler-exchange-offline.postinst
@@ -4,20 +4,21 @@ set -e
. /usr/share/debconf/confmodule
-TALER_HOME="/var/lib/taler"
-
case "${1}" in
configure)
if ! getent group taler-exchange-offline >/dev/null; then
- addgroup --quiet --system taler-exchange-offline
+ addgroup --quiet taler-exchange-offline
fi
if ! getent passwd taler-exchange-offline >/dev/null; then
- adduser --quiet --system \
+ adduser --quiet \
+ --disabled-password \
+ --system \
+ --shell /bin/bash \
+ --home /home/taler-exchange-offline \
--ingroup taler-exchange-offline \
- --no-create-home \
- --home ${TALER_HOME} taler-exchange-offline
+ taler-exchange-offline
fi
;;
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.install b/debian/taler-exchange.install
index 8da445c93..f8fef2c3b 100644
--- a/debian/taler-exchange.install
+++ b/debian/taler-exchange.install
@@ -1,28 +1,40 @@
usr/bin/taler-exchange-aggregator
usr/bin/taler-exchange-closer
-usr/bin/taler-exchange-dbinit
+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-wirewatch*
-usr/share/man/man1/taler-bank*
usr/share/man/man1/taler-exchange-wire-gateway-client*
-usr/share/info/taler-bank*
+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/
-
-usr/share/taler/exchange/pp/*/*
-usr/share/taler/exchange/tos/*/*
diff --git a/debian/taler-exchange.postinst b/debian/taler-exchange.postinst
index 6e01ca2fb..7509a7749 100644
--- a/debian/taler-exchange.postinst
+++ b/debian/taler-exchange.postinst
@@ -9,9 +9,11 @@ _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
@@ -29,10 +31,14 @@ configure)
if ! getent passwd ${_EUSERNAME} >/dev/null; then
adduser --quiet --system --no-create-home --ingroup ${_GROUPNAME} --home ${TALER_HOME} ${_EUSERNAME}
adduser --quiet ${_EUSERNAME} ${_DBGROUPNAME}
+ adduser --quiet ${_EUSERNAME} ${_GROUPNAME}
fi
if ! getent passwd ${_RSECUSERNAME} >/dev/null; then
adduser --quiet --system --no-create-home --ingroup ${_GROUPNAME} --home ${TALER_HOME} ${_RSECUSERNAME}
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
@@ -48,16 +54,20 @@ configure)
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.secret.conf >/dev/null 2>&1; then
+ if ! dpkg-statoverride --list /etc/taler/secrets/exchange-accountcredentials-1.secret.conf >/dev/null 2>&1; then
dpkg-statoverride --add --update \
- ${_WIREUSERNAME} root 460 \
- /etc/taler/secrets/exchange-accountcredentials.secret.conf
+ ${_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} 660 \
+ root ${_DBGROUPNAME} 640 \
/etc/taler/secrets/exchange-db.secret.conf
fi
diff --git a/debian/taler-exchange.postrm b/debian/taler-exchange.postrm
index 6c31a4591..fcde84b58 100644
--- a/debian/taler-exchange.postrm
+++ b/debian/taler-exchange.postrm
@@ -2,18 +2,47 @@
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) ;;
-remove | upgrade | failed-upgrade | abort-install | abort-upgrade | disappear) ;;
+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
- ;;
+ echo "postrm called with unknown argument \`${1}'" >&2
+ exit 1
+ ;;
esac
#DEBHELPER#
diff --git a/debian/taler-exchange.taler-exchange-aggregator.service b/debian/taler-exchange.taler-exchange-aggregator.service
index 683c1a81e..db297270f 100644
--- a/debian/taler-exchange.taler-exchange-aggregator.service
+++ b/debian/taler-exchange.taler-exchange-aggregator.service
@@ -1,15 +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
-RestartSec=100ms
-ExecStart=/usr/bin/taler-exchange-aggregator -c /etc/taler/taler.conf
+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
index 01c52b2d4..ba57522b0 100644
--- a/debian/taler-exchange.taler-exchange-closer.service
+++ b/debian/taler-exchange.taler-exchange-closer.service
@@ -1,15 +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
-RestartSec=100ms
-ExecStart=/usr/bin/taler-exchange-closer -c /etc/taler/taler.conf
+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
index 7db2b1194..cbde72522 100644
--- a/debian/taler-exchange.taler-exchange-httpd.service
+++ b/debian/taler-exchange.taler-exchange-httpd.service
@@ -1,24 +1,35 @@
[Unit]
Description=GNU Taler payment system exchange REST API
AssertPathExists=/run/taler/exchange-httpd
-Requires=taler-exchange-httpd.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
+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 suicides and then
-# needs to be restarted.
+
+# Depending on the configuration, the service process kills itself and then
+# needs to be restarted. Thus no significant delay on restarts.
Restart=always
-# Do not dally on restarts.
+RestartMode=direct
RestartSec=1ms
-ExecStart=/usr/bin/taler-exchange-httpd -c /etc/taler/taler.conf
+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@.service b/debian/taler-exchange.taler-exchange-httpd@.service
index 4235f72e9..c4d010b80 100644
--- a/debian/taler-exchange.taler-exchange-httpd@.service
+++ b/debian/taler-exchange.taler-exchange-httpd@.service
@@ -1,4 +1,9 @@
-% This is a systemd service template.
+# 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
@@ -14,13 +19,15 @@ Type=simple
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
+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-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
index e4898581c..17f1da3f5 100644
--- a/debian/taler-exchange.taler-exchange-secmod-eddsa.service
+++ b/debian/taler-exchange.taler-exchange-secmod-eddsa.service
@@ -8,9 +8,12 @@ User=taler-exchange-secmod-eddsa
Type=simple
Restart=always
RestartSec=100ms
-ExecStart=/usr/bin/taler-exchange-secmod-eddsa -c /etc/taler/taler.conf
+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
index 6c5a3d613..854737d03 100644
--- a/debian/taler-exchange.taler-exchange-secmod-rsa.service
+++ b/debian/taler-exchange.taler-exchange-secmod-rsa.service
@@ -8,9 +8,12 @@ User=taler-exchange-secmod-rsa
Type=simple
Restart=always
RestartSec=100ms
-ExecStart=/usr/bin/taler-exchange-secmod-rsa -c /etc/taler/taler.conf
+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
index b2615b7c9..ffe2f1955 100644
--- a/debian/taler-exchange.taler-exchange-transfer.service
+++ b/debian/taler-exchange.taler-exchange-transfer.service
@@ -1,16 +1,20 @@
[Unit]
Description=Taler Exchange Transfer Service
-After=network.target
+After=network.target postgres.service
PartOf=taler-exchange.target
[Service]
User=taler-exchange-wire
Type=simple
Restart=always
-RestartSec=100ms
-ExecStart=/usr/bin/taler-exchange-transfer -c /etc/taler/taler.conf
+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
index 54704cb80..40103bb51 100644
--- a/debian/taler-exchange.taler-exchange-wirewatch.service
+++ b/debian/taler-exchange.taler-exchange-wirewatch.service
@@ -1,16 +1,20 @@
[Unit]
Description=GNU Taler payment system exchange wirewatch service
-After=network.target
+After=network.target postgres.service
PartOf=taler-exchange.target
[Service]
User=taler-exchange-wire
Type=simple
Restart=always
-RestartSec=100ms
-ExecStart=/usr/bin/taler-exchange-wirewatch -c /etc/taler/taler.conf
+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
index 54704cb80..a2836c6b9 100644
--- a/debian/taler-exchange.taler-exchange-wirewatch@.service
+++ b/debian/taler-exchange.taler-exchange-wirewatch@.service
@@ -1,3 +1,9 @@
+# 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
@@ -7,10 +13,13 @@ PartOf=taler-exchange.target
User=taler-exchange-wire
Type=simple
Restart=always
-RestartSec=100ms
-ExecStart=/usr/bin/taler-exchange-wirewatch -c /etc/taler/taler.conf
+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
index eea37c800..65ec77c1e 100644
--- a/debian/taler-exchange.taler-exchange.target
+++ b/debian/taler-exchange.taler-exchange.target
@@ -6,6 +6,7 @@ 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]
diff --git a/debian/taler-exchange.tmpfiles b/debian/taler-exchange.tmpfiles
index 79554ccf2..c2a796539 100644
--- a/debian/taler-exchange.tmpfiles
+++ b/debian/taler-exchange.tmpfiles
@@ -1,7 +1,8 @@
#Type Path Mode UID GID Age Argument
d /run/taler/exchange-secmod-rsa 0755 taler-exchange-secmod-rsa taler-exchange-secmod - -
+d /run/taler/exchange-secmod-cs 0755 taler-exchange-secmod-cs taler-exchange-secmod - -
d /run/taler/exchange-secmod-eddsa 0755 taler-exchange-secmod-eddsa taler-exchange-secmod - -
d /run/taler/exchange-httpd 0750 taler-exchange-httpd www-data - -
-d /var/lib/taler/exchange-offline 0700 taler-exchange-offline taler-exchange-offline - -
+d /var/lib/taler/exchange-secmod-cs 0700 taler-exchange-secmod-cs taler-exchange-secmod - -
d /var/lib/taler/exchange-secmod-rsa 0700 taler-exchange-secmod-rsa taler-exchange-secmod - -
d /var/lib/taler/exchange-secmod-eddsa 0700 taler-exchange-secmod-eddsa taler-exchange-secmod - -
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/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 70f10c34f..ca973a1a5 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -9,37 +9,56 @@ infoimagedir = $(infodir)/images
man_MANS = \
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-exchange.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-transfer.1 \
+ prebuilt/man/taler-bank-benchmark.1 \
prebuilt/man/taler-exchange-aggregator.1 \
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-wirewatch.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-helper-auditor-wire.1 \
+ prebuilt/man/taler-terms-generator.1 \
+ prebuilt/man/taler-unified-setup.1
info_TEXINFOS = \
prebuilt/texinfo/taler-auditor.texi \
- prebuilt/texinfo/taler-bank.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) \
@@ -47,16 +66,5 @@ EXTRA_DIST = \
$(info_TEXINFOS) \
prebuilt/texinfo/taler-auditor-figures/auditor-db.png \
prebuilt/texinfo/taler-auditor-figures/replication.png \
- prebuilt/texinfo/taler-bank-figures/arch-api.png \
- prebuilt/texinfo/taler-bank-figures/auditor-db.png \
- prebuilt/texinfo/taler-bank-figures/exchange-db.png \
- prebuilt/texinfo/taler-bank-figures/merchant-db.png \
- prebuilt/texinfo/taler-bank-figures/replication.png \
- prebuilt/texinfo/taler-developer-manual-figures/arch-api.png \
- prebuilt/texinfo/taler-developer-manual-figures/auditor-db.png \
- prebuilt/texinfo/taler-developer-manual-figures/exchange-db.png \
- prebuilt/texinfo/taler-developer-manual-figures/merchant-db.png \
- prebuilt/texinfo/taler-developer-manual-figures/replication.png \
- prebuilt/texinfo/taler-exchange-figures/auditor-db.png \
- prebuilt/texinfo/taler-exchange-figures/exchange-db.png\
- prebuilt/texinfo/taler-exchange-figures/replication.png
+ prebuilt/texinfo/taler-exchange-figures/kyc-process.png \
+ prebuilt/texinfo/taler-exchange-figures/exchange-db.png
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 dafe78fa5..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.8.3
+
+# 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,282 +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
#---------------------------------------------------------------------------
+
+# 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
+
+# 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_* \
- */.svn/* \
*/.git/* \
- */perf_* .* \
+ */perf_* \
.* \
- */gnu-taler-error-codes/*
+ .* \
+ */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
+
+# 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 for the AutoGen Definitions output
+# Configuration options related to the DOCBOOK 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
+# 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
+
+# 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/prebuilt b/doc/prebuilt
-Subproject 74d9c44ebc257a3d8b9c2c0a806508bd0cc5269
+Subproject b8d2d2fa2ed2a771880f451725176f256583cb2
diff --git a/doc/system/.gitignore b/doc/system/.gitignore
index 3156fa055..368efd05f 100644
--- a/doc/system/.gitignore
+++ b/doc/system/.gitignore
@@ -1,5 +1,7 @@
thesis.pdf
thesis-*.pdf
+system.pdf
+system-*.pdf
thesis.out
summary/summary-english.pdf
*-converted-to.pdf
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/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-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/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/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/system.tex b/doc/system/system.tex
index b5be63564..16782212a 100644
--- a/doc/system/system.tex
+++ b/doc/system/system.tex
@@ -66,7 +66,7 @@
\usepackage{pdfpages}
-\author{Florian Dold \and the GNU Taler Developers}
+\author{Florian Dold \and The GNU Taler Developers}
\title{The GNU Taler System}
\begin{document}
@@ -79,6 +79,7 @@
\let\thefootnote=\oldthefootnote%
}
+\maketitle
\frontmatter
\include{abstract}
\include{acknowledgements}
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/implementation.tex b/doc/system/taler/implementation.tex
index 9d7cb9a9e..e7d8b9dc7 100644
--- a/doc/system/taler/implementation.tex
+++ b/doc/system/taler/implementation.tex
@@ -1179,7 +1179,7 @@ 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
-missbehavior from an exchange. The goal would be to automatically verify the
+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.
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/libgnurl.m4 b/m4/libgnurl.m4
deleted file mode 100644
index 412709373..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,
- AS_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/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/src/Makefile.am b/src/Makefile.am
index 64d6020f4..e10ecf8d8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -18,16 +18,20 @@ SUBDIRS = \
include \
util \
json \
+ extensions \
curl \
$(PQ_DIR) \
$(SQ_DIR) \
mhd \
+ templating \
bank-lib \
exchangedb \
+ kyclogic \
exchange \
auditordb \
auditor \
lib \
exchange-tools \
+ extensions/age_restriction \
testing \
benchmark
diff --git a/src/auditor/.gitignore b/src/auditor/.gitignore
index 6d25d8bae..11c875dc6 100644
--- a/src/auditor/.gitignore
+++ b/src/auditor/.gitignore
@@ -19,3 +19,10 @@ 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 49f4030ea..381c0b115 100644
--- a/src/auditor/Makefile.am
+++ b/src/auditor/Makefile.am
@@ -16,12 +16,12 @@ clean-local:
bin_PROGRAMS = \
taler-auditor-dbinit \
- taler-auditor-exchange \
taler-auditor-httpd \
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
@@ -65,9 +65,9 @@ taler_auditor_dbinit_CPPFLAGS = \
-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 \
@@ -80,9 +80,9 @@ taler_helper_auditor_reserves_LDADD = \
-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 \
@@ -95,9 +95,9 @@ taler_helper_auditor_coins_LDADD = \
-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 \
@@ -110,9 +110,24 @@ taler_helper_auditor_aggregation_LDADD = \
-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 \
+ $(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_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 \
@@ -125,6 +140,8 @@ taler_helper_auditor_deposits_LDADD = \
-lgnunetutil \
$(XLIB)
+
+
taler_helper_auditor_wire_SOURCES = \
taler-helper-auditor-wire.c
taler_helper_auditor_wire_LDADD = \
@@ -145,7 +162,7 @@ taler_helper_auditor_wire_LDADD = \
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) \
@@ -161,15 +178,6 @@ taler_auditor_httpd_LDADD = \
-lz \
$(XLIB)
-taler_auditor_exchange_SOURCES = \
- taler-auditor-exchange.c
-taler_auditor_exchange_LDADD = \
- $(LIBGCRYPT_LIBS) \
- $(top_builddir)/src/util/libtalerutil.la \
- $(top_builddir)/src/auditordb/libtalerauditordb.la \
- -lgnunetutil \
- $(XLIB)
-
taler_auditor_sync_SOURCES = \
taler-auditor-sync.c
taler_auditor_sync_LDADD = \
@@ -189,28 +197,22 @@ taler_auditor_sync_CPPFLAGS = \
check_SCRIPTS = \
test-auditor.sh \
+ test-kyc.sh \
test-revocation.sh \
test-sync.sh
.NOTPARALLEL:
-# Disabled for now: need working wallet first!
# 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.mpub \
- revoke-basedb.age \
- revoke-basedb.sql \
- 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 8a55c2885..000000000
--- a/src/auditor/auditor-basedb.age
+++ /dev/null
@@ -1 +0,0 @@
-1638356946
diff --git a/src/auditor/auditor-basedb.fees b/src/auditor/auditor-basedb.fees
deleted file mode 100644
index ef2a6d121..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 13f7e591d..000000000
--- a/src/auditor/auditor-basedb.mpub
+++ /dev/null
@@ -1 +0,0 @@
-R8JBA9089F3YRRSQDPWB92CZ5V1V0J1BPVW8J1B0P8VCVH71CYY0
diff --git a/src/auditor/auditor-basedb.sql b/src/auditor/auditor-basedb.sql
deleted file mode 100644
index fbbba67b0..000000000
--- a/src/auditor/auditor-basedb.sql
+++ /dev/null
@@ -1,8597 +0,0 @@
---
--- PostgreSQL database dump
---
-
--- Dumped from database version 13.4
--- Dumped by pg_dump version 13.4
-
-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 xmloption = content;
-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: 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_table_access_method = heap;
-
---
--- 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: COLUMN aggregation_tracking.wtid_raw; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.aggregation_tracking.wtid_raw IS 'We first create entries in the aggregation_tracking table and then finally the wire_out entry once we know the total amount. Hence the constraint must be deferrable and we cannot use a wireout_uuid here, because we do not have it when these rows are created. Changing the logic to first INSERT a dummy row into wire_out and then UPDATEing that row in the same transaction would theoretically reduce per-deposit storage costs by 5 percent (24/~460 bytes).';
-
-
---
--- 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 bigint 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 NOT NULL,
- 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_denom_sigs; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_denom_sigs (
- auditor_denom_serial bigint NOT NULL,
- auditor_uuid bigint NOT NULL,
- denominations_serial bigint NOT NULL,
- auditor_sig bytea,
- CONSTRAINT auditor_denom_sigs_auditor_sig_check CHECK ((length(auditor_sig) = 64))
-);
-
-
---
--- Name: TABLE auditor_denom_sigs; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_denom_sigs IS 'Table with auditor signatures on exchange denomination keys.';
-
-
---
--- Name: COLUMN auditor_denom_sigs.auditor_uuid; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditor_denom_sigs.auditor_uuid IS 'Identifies the auditor.';
-
-
---
--- Name: COLUMN auditor_denom_sigs.denominations_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditor_denom_sigs.denominations_serial IS 'Denomination the signature is for.';
-
-
---
--- Name: COLUMN auditor_denom_sigs.auditor_sig; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditor_denom_sigs.auditor_sig IS 'Signature of the auditor, of purpose TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS.';
-
-
---
--- Name: auditor_denom_sigs_auditor_denom_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auditor_denom_sigs_auditor_denom_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auditor_denom_sigs_auditor_denom_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auditor_denom_sigs_auditor_denom_serial_seq OWNED BY public.auditor_denom_sigs.auditor_denom_serial;
-
-
---
--- 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,
- CONSTRAINT auditor_denomination_pending_denom_pub_hash_check CHECK ((length(denom_pub_hash) = 64))
-);
-
-
---
--- 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_exchange_signkeys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_exchange_signkeys (
- master_pub bytea NOT NULL,
- 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 NOT NULL,
- 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 NOT NULL,
- 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 NOT NULL,
- 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 NOT NULL,
- 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 NOT NULL,
- 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 NOT NULL,
- 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: auditors; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditors (
- auditor_uuid bigint NOT NULL,
- auditor_pub bytea NOT NULL,
- auditor_name character varying NOT NULL,
- auditor_url character varying NOT NULL,
- is_active boolean NOT NULL,
- last_change bigint NOT NULL,
- CONSTRAINT auditors_auditor_pub_check CHECK ((length(auditor_pub) = 32))
-);
-
-
---
--- Name: TABLE auditors; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.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.';
-
-
---
--- Name: COLUMN auditors.auditor_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditors.auditor_pub IS 'Public key of the auditor.';
-
-
---
--- Name: COLUMN auditors.auditor_url; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditors.auditor_url IS 'The base URL of the auditor.';
-
-
---
--- Name: COLUMN auditors.is_active; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditors.is_active IS 'true if we are currently supporting the use of this auditor.';
-
-
---
--- Name: COLUMN auditors.last_change; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditors.last_change IS 'Latest time when active status changed. Used to detect replays of old messages.';
-
-
---
--- Name: auditors_auditor_uuid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auditors_auditor_uuid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auditors_auditor_uuid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auditors_auditor_uuid_seq OWNED BY public.auditors.auditor_uuid;
-
-
---
--- 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 bigint 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(150) 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 bigint 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 bigint 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,
- denominations_serial bigint 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 (
- denominations_serial bigint NOT NULL,
- denom_pub_hash bytea NOT NULL,
- denom_type integer DEFAULT 1 NOT NULL,
- age_restrictions integer DEFAULT 0 NOT NULL,
- denom_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_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 valid denominations the exchange knows about.';
-
-
---
--- Name: COLUMN denominations.denominations_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.denominations.denominations_serial IS 'needed for exchange-auditor replication logic';
-
-
---
--- Name: COLUMN denominations.denom_type; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.denominations.denom_type IS 'determines cipher type for blind signatures used with this denomination; 0 is for RSA';
-
-
---
--- Name: COLUMN denominations.age_restrictions; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.denominations.age_restrictions IS 'bitmask with the age restrictions that are being used for this denomination; 0 if denomination does not support the use of age restrictions';
-
-
---
--- Name: denominations_denominations_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.denominations_denominations_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: denominations_denominations_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.denominations_denominations_serial_seq OWNED BY public.denominations.denominations_serial;
-
-
---
--- Name: deposit_confirmations; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.deposit_confirmations (
- master_pub bytea NOT NULL,
- serial_id bigint NOT NULL,
- h_contract_terms bytea NOT NULL,
- h_extensions bytea NOT NULL,
- h_wire bytea NOT NULL,
- exchange_timestamp bigint NOT NULL,
- refund_deadline bigint NOT NULL,
- wire_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_contract_terms_check1 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,
- shard bigint NOT NULL,
- known_coin_id bigint NOT NULL,
- amount_with_fee_val bigint NOT NULL,
- amount_with_fee_frac integer NOT NULL,
- wallet_timestamp bigint NOT NULL,
- exchange_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,
- coin_sig bytea NOT NULL,
- wire_salt bytea NOT NULL,
- wire_target_serial_id bigint NOT NULL,
- tiny boolean DEFAULT false NOT NULL,
- done boolean DEFAULT false NOT NULL,
- extension_blocked boolean DEFAULT false NOT NULL,
- extension_details_serial_id bigint,
- CONSTRAINT deposits_coin_sig_check CHECK ((length(coin_sig) = 64)),
- CONSTRAINT deposits_h_contract_terms_check CHECK ((length(h_contract_terms) = 64)),
- CONSTRAINT deposits_merchant_pub_check CHECK ((length(merchant_pub) = 32)),
- CONSTRAINT deposits_wire_salt_check CHECK ((length(wire_salt) = 16))
-);
-
-
---
--- 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.shard; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.deposits.shard IS 'Used for load sharding. Should be set based on h_payto and merchant_pub. 64-bit value because we need an *unsigned* 32-bit value.';
-
-
---
--- Name: COLUMN deposits.wire_salt; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.deposits.wire_salt IS 'Salt used when hashing the payto://-URI to get the h_wire';
-
-
---
--- Name: COLUMN deposits.wire_target_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.deposits.wire_target_serial_id IS 'Identifies the target bank account and KYC status';
-
-
---
--- 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: COLUMN deposits.extension_blocked; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.deposits.extension_blocked IS 'True if the aggregation of the deposit is currently blocked by some extension mechanism. Used to filter out deposits that must not be processed by the canonical deposit logic.';
-
-
---
--- Name: COLUMN deposits.extension_details_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.deposits.extension_details_serial_id IS 'References extensions table, NULL if extensions are not used';
-
-
---
--- 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 bigint 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_sign_keys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.exchange_sign_keys (
- esk_serial bigint NOT NULL,
- exchange_pub bytea NOT NULL,
- master_sig bytea NOT NULL,
- valid_from bigint NOT NULL,
- expire_sign bigint NOT NULL,
- expire_legal bigint NOT NULL,
- CONSTRAINT exchange_sign_keys_exchange_pub_check CHECK ((length(exchange_pub) = 32)),
- CONSTRAINT exchange_sign_keys_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE exchange_sign_keys; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.exchange_sign_keys IS 'Table with master public key signatures on exchange online signing keys.';
-
-
---
--- Name: COLUMN exchange_sign_keys.exchange_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.exchange_sign_keys.exchange_pub IS 'Public online signing key of the exchange.';
-
-
---
--- Name: COLUMN exchange_sign_keys.master_sig; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.exchange_sign_keys.master_sig IS 'Signature affirming the validity of the signing key of purpose TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY.';
-
-
---
--- Name: COLUMN exchange_sign_keys.valid_from; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.exchange_sign_keys.valid_from IS 'Time when this online signing key will first be used to sign messages.';
-
-
---
--- Name: COLUMN exchange_sign_keys.expire_sign; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.exchange_sign_keys.expire_sign IS 'Time when this online signing key will no longer be used to sign.';
-
-
---
--- Name: COLUMN exchange_sign_keys.expire_legal; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.exchange_sign_keys.expire_legal IS 'Time when this online signing key legally expires.';
-
-
---
--- Name: exchange_sign_keys_esk_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.exchange_sign_keys_esk_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: exchange_sign_keys_esk_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.exchange_sign_keys_esk_serial_seq OWNED BY public.exchange_sign_keys.esk_serial;
-
-
---
--- Name: extension_details; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.extension_details (
- extension_details_serial_id bigint NOT NULL,
- extension_options character varying
-);
-
-
---
--- Name: TABLE extension_details; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.extension_details IS 'Extensions that were provided with deposits (not yet used).';
-
-
---
--- Name: COLUMN extension_details.extension_options; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.extension_details.extension_options IS 'JSON object with options set that the exchange needs to consider when executing a deposit. Supported details depend on the extensions supported by the exchange.';
-
-
---
--- Name: extension_details_extension_details_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.extension_details_extension_details_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: extension_details_extension_details_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.extension_details_extension_details_serial_id_seq OWNED BY public.extension_details.extension_details_serial_id;
-
-
---
--- Name: known_coins; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.known_coins (
- known_coin_id bigint NOT NULL,
- coin_pub bytea NOT NULL,
- age_hash bytea,
- denominations_serial bigint NOT NULL,
- denom_sig bytea NOT NULL,
- CONSTRAINT known_coins_age_hash_check CHECK ((length(age_hash) = 32)),
- 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: COLUMN known_coins.coin_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.known_coins.coin_pub IS 'EdDSA public key of the coin';
-
-
---
--- Name: COLUMN known_coins.age_hash; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.known_coins.age_hash IS 'Optional hash for age restrictions as per DD 24 (active if denom_type has the respective bit set)';
-
-
---
--- Name: COLUMN known_coins.denom_sig; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.known_coins.denom_sig IS '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.';
-
-
---
--- Name: known_coins_known_coin_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.known_coins_known_coin_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: known_coins_known_coin_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.known_coins_known_coin_id_seq OWNED BY public.known_coins.known_coin_id;
-
-
---
--- Name: merchant_accounts; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_accounts (
- account_serial bigint NOT NULL,
- merchant_serial bigint NOT NULL,
- h_wire bytea NOT NULL,
- salt bytea NOT NULL,
- payto_uri character varying NOT NULL,
- active boolean NOT NULL,
- CONSTRAINT merchant_accounts_h_wire_check CHECK ((length(h_wire) = 64)),
- CONSTRAINT merchant_accounts_salt_check CHECK ((length(salt) = 16))
-);
-
-
---
--- Name: TABLE merchant_accounts; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_accounts IS 'bank accounts of the instances';
-
-
---
--- Name: COLUMN merchant_accounts.h_wire; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_accounts.h_wire IS 'salted hash of payto_uri';
-
-
---
--- Name: COLUMN merchant_accounts.salt; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_accounts.salt IS 'salt used when hashing payto_uri into h_wire';
-
-
---
--- Name: COLUMN merchant_accounts.payto_uri; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_accounts.payto_uri IS 'payto URI of a merchant bank account';
-
-
---
--- Name: COLUMN merchant_accounts.active; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_accounts.active IS 'true if we actively use this bank account, false if it is just kept around for older contracts to refer to';
-
-
---
--- Name: merchant_accounts_account_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_accounts_account_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_accounts_account_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_accounts_account_serial_seq OWNED BY public.merchant_accounts.account_serial;
-
-
---
--- Name: merchant_contract_terms; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_contract_terms (
- order_serial bigint NOT NULL,
- merchant_serial bigint NOT NULL,
- order_id character varying NOT NULL,
- contract_terms bytea NOT NULL,
- h_contract_terms bytea NOT NULL,
- creation_time bigint NOT NULL,
- pay_deadline bigint NOT NULL,
- refund_deadline bigint NOT NULL,
- paid boolean DEFAULT false NOT NULL,
- wired boolean DEFAULT false NOT NULL,
- fulfillment_url character varying,
- session_id character varying DEFAULT ''::character varying NOT NULL,
- claim_token bytea NOT NULL,
- CONSTRAINT merchant_contract_terms_claim_token_check CHECK ((length(claim_token) = 16)),
- CONSTRAINT merchant_contract_terms_h_contract_terms_check CHECK ((length(h_contract_terms) = 64))
-);
-
-
---
--- Name: TABLE merchant_contract_terms; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_contract_terms IS 'Contracts are orders that have been claimed by a wallet';
-
-
---
--- Name: COLUMN merchant_contract_terms.merchant_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.merchant_serial IS 'Identifies the instance offering the contract';
-
-
---
--- Name: COLUMN merchant_contract_terms.order_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.order_id IS 'Not a foreign key into merchant_orders because paid contracts persist after expiration';
-
-
---
--- Name: COLUMN merchant_contract_terms.contract_terms; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.contract_terms IS 'These contract terms include the wallet nonce';
-
-
---
--- Name: COLUMN merchant_contract_terms.h_contract_terms; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.h_contract_terms IS 'Hash over contract_terms';
-
-
---
--- Name: COLUMN merchant_contract_terms.pay_deadline; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.pay_deadline IS 'How long is the offer valid. After this time, the order can be garbage collected';
-
-
---
--- Name: COLUMN merchant_contract_terms.refund_deadline; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.refund_deadline IS 'By what times do refunds have to be approved (useful to reject refund requests)';
-
-
---
--- Name: COLUMN merchant_contract_terms.paid; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.paid IS 'true implies the customer paid for this contract; order should be DELETEd from merchant_orders once paid is set to release merchant_order_locks; paid remains true even if the payment was later refunded';
-
-
---
--- Name: COLUMN merchant_contract_terms.wired; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.wired IS 'true implies the exchange wired us the full amount for all non-refunded payments under this contract';
-
-
---
--- Name: COLUMN merchant_contract_terms.fulfillment_url; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.fulfillment_url IS 'also included in contract_terms, but we need it here to SELECT on it during repurchase detection; can be NULL if the contract has no fulfillment URL';
-
-
---
--- Name: COLUMN merchant_contract_terms.session_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.session_id IS 'last session_id from we confirmed the paying client to use, empty string for none';
-
-
---
--- Name: COLUMN merchant_contract_terms.claim_token; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.claim_token IS 'Token optionally used to access the status of the order. All zeros (not NULL) if not used';
-
-
---
--- Name: merchant_deposit_to_transfer; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_deposit_to_transfer (
- deposit_serial bigint NOT NULL,
- coin_contribution_value_val bigint NOT NULL,
- coin_contribution_value_frac integer NOT NULL,
- credit_serial bigint NOT NULL,
- execution_time bigint NOT NULL,
- signkey_serial bigint NOT NULL,
- exchange_sig bytea NOT NULL,
- CONSTRAINT merchant_deposit_to_transfer_exchange_sig_check CHECK ((length(exchange_sig) = 64))
-);
-
-
---
--- Name: TABLE merchant_deposit_to_transfer; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_deposit_to_transfer IS 'Mapping of deposits to (possibly unconfirmed) wire transfers; NOTE: not used yet';
-
-
---
--- Name: COLUMN merchant_deposit_to_transfer.execution_time; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_deposit_to_transfer.execution_time IS 'Execution time as claimed by the exchange, roughly matches time seen by merchant';
-
-
---
--- Name: merchant_deposits; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_deposits (
- deposit_serial bigint NOT NULL,
- order_serial bigint,
- deposit_timestamp bigint 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_serial bigint NOT NULL,
- exchange_sig bytea NOT NULL,
- account_serial bigint NOT NULL,
- CONSTRAINT merchant_deposits_coin_pub_check CHECK ((length(coin_pub) = 32)),
- CONSTRAINT merchant_deposits_exchange_sig_check CHECK ((length(exchange_sig) = 64))
-);
-
-
---
--- Name: TABLE merchant_deposits; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_deposits IS 'Refunds approved by the merchant (backoffice) logic, excludes abort refunds';
-
-
---
--- Name: COLUMN merchant_deposits.deposit_timestamp; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_deposits.deposit_timestamp IS 'Time when the exchange generated the deposit confirmation';
-
-
---
--- Name: COLUMN merchant_deposits.wire_fee_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_deposits.wire_fee_val IS 'We MAY want to see if we should try to get this via merchant_exchange_wire_fees (not sure, may be too complicated with the date range, etc.)';
-
-
---
--- Name: COLUMN merchant_deposits.signkey_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_deposits.signkey_serial IS 'Online signing key of the exchange on the deposit confirmation';
-
-
---
--- Name: COLUMN merchant_deposits.exchange_sig; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_deposits.exchange_sig IS 'Signature of the exchange over the deposit confirmation';
-
-
---
--- Name: merchant_deposits_deposit_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_deposits_deposit_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_deposits_deposit_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_deposits_deposit_serial_seq OWNED BY public.merchant_deposits.deposit_serial;
-
-
---
--- Name: merchant_exchange_signing_keys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_exchange_signing_keys (
- signkey_serial bigint NOT NULL,
- master_pub bytea NOT NULL,
- exchange_pub bytea NOT NULL,
- start_date bigint NOT NULL,
- expire_date bigint NOT NULL,
- end_date bigint NOT NULL,
- master_sig bytea NOT NULL,
- CONSTRAINT merchant_exchange_signing_keys_exchange_pub_check CHECK ((length(exchange_pub) = 32)),
- CONSTRAINT merchant_exchange_signing_keys_master_pub_check CHECK ((length(master_pub) = 32)),
- CONSTRAINT merchant_exchange_signing_keys_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE merchant_exchange_signing_keys; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_exchange_signing_keys IS 'Here we store proofs of the exchange online signing keys being signed by the exchange master key';
-
-
---
--- Name: COLUMN merchant_exchange_signing_keys.master_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_exchange_signing_keys.master_pub IS 'Master public key of the exchange with these online signing keys';
-
-
---
--- Name: merchant_exchange_signing_keys_signkey_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_exchange_signing_keys_signkey_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_exchange_signing_keys_signkey_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_exchange_signing_keys_signkey_serial_seq OWNED BY public.merchant_exchange_signing_keys.signkey_serial;
-
-
---
--- Name: merchant_exchange_wire_fees; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_exchange_wire_fees (
- wirefee_serial bigint NOT NULL,
- master_pub bytea NOT NULL,
- h_wire_method bytea 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 merchant_exchange_wire_fees_h_wire_method_check CHECK ((length(h_wire_method) = 64)),
- CONSTRAINT merchant_exchange_wire_fees_master_pub_check CHECK ((length(master_pub) = 32)),
- CONSTRAINT merchant_exchange_wire_fees_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE merchant_exchange_wire_fees; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_exchange_wire_fees IS 'Here we store proofs of the wire fee structure of the various exchanges';
-
-
---
--- Name: COLUMN merchant_exchange_wire_fees.master_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_exchange_wire_fees.master_pub IS 'Master public key of the exchange with these wire fees';
-
-
---
--- Name: merchant_exchange_wire_fees_wirefee_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_exchange_wire_fees_wirefee_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_exchange_wire_fees_wirefee_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_exchange_wire_fees_wirefee_serial_seq OWNED BY public.merchant_exchange_wire_fees.wirefee_serial;
-
-
---
--- Name: merchant_instances; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_instances (
- merchant_serial bigint NOT NULL,
- merchant_pub bytea NOT NULL,
- merchant_id character varying NOT NULL,
- merchant_name character varying NOT NULL,
- address bytea NOT NULL,
- jurisdiction bytea NOT NULL,
- default_max_deposit_fee_val bigint NOT NULL,
- default_max_deposit_fee_frac integer NOT NULL,
- default_max_wire_fee_val bigint NOT NULL,
- default_max_wire_fee_frac integer NOT NULL,
- default_wire_fee_amortization integer NOT NULL,
- default_wire_transfer_delay bigint NOT NULL,
- default_pay_delay bigint NOT NULL,
- auth_hash bytea,
- auth_salt bytea,
- CONSTRAINT merchant_instances_auth_hash_check CHECK ((length(auth_hash) = 64)),
- CONSTRAINT merchant_instances_auth_salt_check CHECK ((length(auth_salt) = 32)),
- CONSTRAINT merchant_instances_merchant_pub_check CHECK ((length(merchant_pub) = 32))
-);
-
-
---
--- Name: TABLE merchant_instances; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_instances IS 'all the instances supported by this backend';
-
-
---
--- Name: COLUMN merchant_instances.merchant_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_instances.merchant_id IS 'identifier of the merchant as used in the base URL (required)';
-
-
---
--- Name: COLUMN merchant_instances.merchant_name; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_instances.merchant_name IS 'legal name of the merchant as a simple string (required)';
-
-
---
--- Name: COLUMN merchant_instances.address; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_instances.address IS 'physical address of the merchant as a Location in JSON format (required)';
-
-
---
--- Name: COLUMN merchant_instances.jurisdiction; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_instances.jurisdiction IS 'jurisdiction of the merchant as a Location in JSON format (required)';
-
-
---
--- Name: COLUMN merchant_instances.auth_hash; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_instances.auth_hash IS 'hash used for merchant back office Authorization, NULL for no check';
-
-
---
--- Name: COLUMN merchant_instances.auth_salt; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_instances.auth_salt IS 'salt to use when hashing Authorization header before comparing with auth_hash';
-
-
---
--- Name: merchant_instances_merchant_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_instances_merchant_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_instances_merchant_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_instances_merchant_serial_seq OWNED BY public.merchant_instances.merchant_serial;
-
-
---
--- Name: merchant_inventory; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_inventory (
- product_serial bigint NOT NULL,
- merchant_serial bigint NOT NULL,
- product_id character varying NOT NULL,
- description character varying NOT NULL,
- description_i18n bytea NOT NULL,
- unit character varying NOT NULL,
- image bytea NOT NULL,
- taxes bytea NOT NULL,
- price_val bigint NOT NULL,
- price_frac integer NOT NULL,
- total_stock bigint NOT NULL,
- total_sold bigint DEFAULT 0 NOT NULL,
- total_lost bigint DEFAULT 0 NOT NULL,
- address bytea NOT NULL,
- next_restock bigint NOT NULL
-);
-
-
---
--- Name: TABLE merchant_inventory; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_inventory IS 'products offered by the merchant (may be incomplete, frontend can override)';
-
-
---
--- Name: COLUMN merchant_inventory.description; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.description IS 'Human-readable product description';
-
-
---
--- Name: COLUMN merchant_inventory.description_i18n; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.description_i18n IS 'JSON map from IETF BCP 47 language tags to localized descriptions';
-
-
---
--- Name: COLUMN merchant_inventory.unit; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.unit IS 'Unit of sale for the product (liters, kilograms, packages)';
-
-
---
--- Name: COLUMN merchant_inventory.image; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.image IS 'NOT NULL, but can be 0 bytes; must contain an ImageDataUrl';
-
-
---
--- Name: COLUMN merchant_inventory.taxes; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.taxes IS 'JSON array containing taxes the merchant pays, must be JSON, but can be just "[]"';
-
-
---
--- Name: COLUMN merchant_inventory.price_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.price_val IS 'Current price of one unit of the product';
-
-
---
--- Name: COLUMN merchant_inventory.total_stock; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.total_stock IS 'A value of -1 is used for unlimited (electronic good), may never be lowered';
-
-
---
--- Name: COLUMN merchant_inventory.total_sold; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.total_sold IS 'Number of products sold, must be below total_stock, non-negative, may never be lowered';
-
-
---
--- Name: COLUMN merchant_inventory.total_lost; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.total_lost IS 'Number of products that used to be in stock but were lost (spoiled, damaged), may never be lowered; total_stock >= total_sold + total_lost must always hold';
-
-
---
--- Name: COLUMN merchant_inventory.address; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.address IS 'JSON formatted Location of where the product is stocked';
-
-
---
--- Name: COLUMN merchant_inventory.next_restock; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.next_restock IS 'GNUnet absolute time indicating when the next restock is expected. 0 for unknown.';
-
-
---
--- Name: merchant_inventory_locks; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_inventory_locks (
- product_serial bigint NOT NULL,
- lock_uuid bytea NOT NULL,
- total_locked bigint NOT NULL,
- expiration bigint NOT NULL,
- CONSTRAINT merchant_inventory_locks_lock_uuid_check CHECK ((length(lock_uuid) = 16))
-);
-
-
---
--- Name: TABLE merchant_inventory_locks; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_inventory_locks IS 'locks on inventory helt by shopping carts; note that locks MAY not be honored if merchants increase total_lost for inventory';
-
-
---
--- Name: COLUMN merchant_inventory_locks.total_locked; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory_locks.total_locked IS 'how many units of the product does this lock reserve';
-
-
---
--- Name: COLUMN merchant_inventory_locks.expiration; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory_locks.expiration IS 'when does this lock automatically expire (if no order is created)';
-
-
---
--- Name: merchant_inventory_product_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_inventory_product_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_inventory_product_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_inventory_product_serial_seq OWNED BY public.merchant_inventory.product_serial;
-
-
---
--- Name: merchant_keys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_keys (
- merchant_priv bytea NOT NULL,
- merchant_serial bigint NOT NULL,
- CONSTRAINT merchant_keys_merchant_priv_check CHECK ((length(merchant_priv) = 32))
-);
-
-
---
--- Name: TABLE merchant_keys; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_keys IS 'private keys of instances that have not been deleted';
-
-
---
--- Name: merchant_kyc; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_kyc (
- kyc_serial_id bigint NOT NULL,
- kyc_timestamp bigint NOT NULL,
- kyc_ok boolean DEFAULT false NOT NULL,
- exchange_sig bytea,
- exchange_pub bytea,
- exchange_kyc_serial bigint DEFAULT 0 NOT NULL,
- account_serial bigint NOT NULL,
- exchange_url character varying NOT NULL,
- CONSTRAINT merchant_kyc_exchange_pub_check CHECK ((length(exchange_pub) = 32)),
- CONSTRAINT merchant_kyc_exchange_sig_check CHECK ((length(exchange_sig) = 64))
-);
-
-
---
--- Name: TABLE merchant_kyc; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_kyc IS 'Status of the KYC process of a merchant account at an exchange';
-
-
---
--- Name: COLUMN merchant_kyc.kyc_timestamp; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_kyc.kyc_timestamp IS 'Last time we checked our KYC status at the exchange. Useful to re-check if the status is very stale. Also the timestamp used for the exchange signature (if present).';
-
-
---
--- Name: COLUMN merchant_kyc.kyc_ok; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_kyc.kyc_ok IS 'true if the KYC check was passed successfully';
-
-
---
--- Name: COLUMN merchant_kyc.exchange_sig; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_kyc.exchange_sig IS 'signature of the exchange affirming the KYC passed (or NULL if exchange does not require KYC or not kyc_ok)';
-
-
---
--- Name: COLUMN merchant_kyc.exchange_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_kyc.exchange_pub IS 'public key used with exchange_sig (or NULL if exchange_sig is NULL)';
-
-
---
--- Name: COLUMN merchant_kyc.exchange_kyc_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_kyc.exchange_kyc_serial IS 'Number to use in the KYC-endpoints of the exchange to check the KYC status or begin the KYC process. 0 if we do not know it yet.';
-
-
---
--- Name: COLUMN merchant_kyc.account_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_kyc.account_serial IS 'Which bank account of the merchant is the KYC status for';
-
-
---
--- Name: COLUMN merchant_kyc.exchange_url; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_kyc.exchange_url IS 'Which exchange base URL is this KYC status valid for';
-
-
---
--- Name: merchant_kyc_kyc_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_kyc_kyc_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_kyc_kyc_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_kyc_kyc_serial_id_seq OWNED BY public.merchant_kyc.kyc_serial_id;
-
-
---
--- Name: merchant_order_locks; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_order_locks (
- product_serial bigint NOT NULL,
- total_locked bigint NOT NULL,
- order_serial bigint NOT NULL
-);
-
-
---
--- Name: TABLE merchant_order_locks; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_order_locks IS 'locks on orders awaiting claim and payment; note that locks MAY not be honored if merchants increase total_lost for inventory';
-
-
---
--- Name: COLUMN merchant_order_locks.total_locked; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_order_locks.total_locked IS 'how many units of the product does this lock reserve';
-
-
---
--- Name: merchant_orders; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_orders (
- order_serial bigint NOT NULL,
- merchant_serial bigint NOT NULL,
- order_id character varying NOT NULL,
- claim_token bytea NOT NULL,
- h_post_data bytea NOT NULL,
- pay_deadline bigint NOT NULL,
- creation_time bigint NOT NULL,
- contract_terms bytea NOT NULL,
- CONSTRAINT merchant_orders_claim_token_check CHECK ((length(claim_token) = 16)),
- CONSTRAINT merchant_orders_h_post_data_check CHECK ((length(h_post_data) = 64))
-);
-
-
---
--- Name: TABLE merchant_orders; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_orders IS 'Orders we offered to a customer, but that have not yet been claimed';
-
-
---
--- Name: COLUMN merchant_orders.merchant_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_orders.merchant_serial IS 'Identifies the instance offering the contract';
-
-
---
--- Name: COLUMN merchant_orders.claim_token; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_orders.claim_token IS 'Token optionally used to authorize the wallet to claim the order. All zeros (not NULL) if not used';
-
-
---
--- Name: COLUMN merchant_orders.h_post_data; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_orders.h_post_data IS 'Hash of the POST request that created this order, for idempotency checks';
-
-
---
--- Name: COLUMN merchant_orders.pay_deadline; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_orders.pay_deadline IS 'How long is the offer valid. After this time, the order can be garbage collected';
-
-
---
--- Name: COLUMN merchant_orders.contract_terms; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_orders.contract_terms IS 'Claiming changes the contract_terms, hence we have no hash of the terms in this table';
-
-
---
--- Name: merchant_orders_order_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_orders_order_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_orders_order_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_orders_order_serial_seq OWNED BY public.merchant_orders.order_serial;
-
-
---
--- Name: merchant_refund_proofs; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_refund_proofs (
- refund_serial bigint NOT NULL,
- exchange_sig bytea NOT NULL,
- signkey_serial bigint NOT NULL,
- CONSTRAINT merchant_refund_proofs_exchange_sig_check CHECK ((length(exchange_sig) = 64))
-);
-
-
---
--- Name: TABLE merchant_refund_proofs; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_refund_proofs IS 'Refunds confirmed by the exchange (not all approved refunds are grabbed by the wallet)';
-
-
---
--- Name: merchant_refunds; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_refunds (
- refund_serial bigint NOT NULL,
- order_serial bigint NOT NULL,
- rtransaction_id bigint NOT NULL,
- refund_timestamp bigint NOT NULL,
- coin_pub bytea NOT NULL,
- reason character varying NOT NULL,
- refund_amount_val bigint NOT NULL,
- refund_amount_frac integer NOT NULL
-);
-
-
---
--- Name: COLUMN merchant_refunds.rtransaction_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_refunds.rtransaction_id IS 'Needed for uniqueness in case a refund is increased for the same order';
-
-
---
--- Name: COLUMN merchant_refunds.refund_timestamp; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_refunds.refund_timestamp IS 'Needed for grouping of refunds in the wallet UI; has no semantics in the protocol (only for UX), but should be from the time when the merchant internally approved the refund';
-
-
---
--- Name: merchant_refunds_refund_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_refunds_refund_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_refunds_refund_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_refunds_refund_serial_seq OWNED BY public.merchant_refunds.refund_serial;
-
-
---
--- Name: merchant_tip_pickup_signatures; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tip_pickup_signatures (
- pickup_serial bigint NOT NULL,
- coin_offset integer NOT NULL,
- blind_sig bytea NOT NULL
-);
-
-
---
--- Name: TABLE merchant_tip_pickup_signatures; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_tip_pickup_signatures IS 'blind signatures we got from the exchange during the tip pickup';
-
-
---
--- Name: merchant_tip_pickups; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tip_pickups (
- pickup_serial bigint NOT NULL,
- tip_serial bigint 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: TABLE merchant_tip_pickups; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_tip_pickups IS 'tips that have been picked up';
-
-
---
--- Name: merchant_tip_pickups_pickup_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_tip_pickups_pickup_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_tip_pickups_pickup_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_tip_pickups_pickup_serial_seq OWNED BY public.merchant_tip_pickups.pickup_serial;
-
-
---
--- Name: merchant_tip_reserve_keys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tip_reserve_keys (
- reserve_serial bigint NOT NULL,
- reserve_priv bytea NOT NULL,
- exchange_url character varying NOT NULL,
- payto_uri character varying,
- CONSTRAINT merchant_tip_reserve_keys_reserve_priv_check CHECK ((length(reserve_priv) = 32))
-);
-
-
---
--- Name: COLUMN merchant_tip_reserve_keys.payto_uri; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tip_reserve_keys.payto_uri IS 'payto:// URI used to fund the reserve, may be NULL once reserve is funded';
-
-
---
--- Name: merchant_tip_reserves; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tip_reserves (
- reserve_serial bigint NOT NULL,
- reserve_pub bytea NOT NULL,
- merchant_serial bigint NOT NULL,
- creation_time bigint NOT NULL,
- expiration bigint NOT NULL,
- merchant_initial_balance_val bigint NOT NULL,
- merchant_initial_balance_frac integer NOT NULL,
- exchange_initial_balance_val bigint DEFAULT 0 NOT NULL,
- exchange_initial_balance_frac integer DEFAULT 0 NOT NULL,
- tips_committed_val bigint DEFAULT 0 NOT NULL,
- tips_committed_frac integer DEFAULT 0 NOT NULL,
- tips_picked_up_val bigint DEFAULT 0 NOT NULL,
- tips_picked_up_frac integer DEFAULT 0 NOT NULL,
- CONSTRAINT merchant_tip_reserves_reserve_pub_check CHECK ((length(reserve_pub) = 32))
-);
-
-
---
--- Name: TABLE merchant_tip_reserves; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_tip_reserves IS 'private keys of reserves that have not been deleted';
-
-
---
--- Name: COLUMN merchant_tip_reserves.expiration; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tip_reserves.expiration IS 'FIXME: EXCHANGE API needs to tell us when reserves close if we are to compute this';
-
-
---
--- Name: COLUMN merchant_tip_reserves.merchant_initial_balance_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tip_reserves.merchant_initial_balance_val IS 'Set to the initial balance the merchant told us when creating the reserve';
-
-
---
--- Name: COLUMN merchant_tip_reserves.exchange_initial_balance_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tip_reserves.exchange_initial_balance_val IS 'Set to the initial balance the exchange told us when we queried the reserve status';
-
-
---
--- Name: COLUMN merchant_tip_reserves.tips_committed_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tip_reserves.tips_committed_val IS 'Amount of outstanding approved tips that have not been picked up';
-
-
---
--- Name: COLUMN merchant_tip_reserves.tips_picked_up_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tip_reserves.tips_picked_up_val IS 'Total amount tips that have been picked up from this reserve';
-
-
---
--- Name: merchant_tip_reserves_reserve_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_tip_reserves_reserve_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_tip_reserves_reserve_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_tip_reserves_reserve_serial_seq OWNED BY public.merchant_tip_reserves.reserve_serial;
-
-
---
--- Name: merchant_tips; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tips (
- tip_serial bigint NOT NULL,
- reserve_serial bigint NOT NULL,
- tip_id bytea NOT NULL,
- justification character varying NOT NULL,
- next_url character varying NOT NULL,
- expiration bigint NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- picked_up_val bigint DEFAULT 0 NOT NULL,
- picked_up_frac integer DEFAULT 0 NOT NULL,
- was_picked_up boolean DEFAULT false NOT NULL,
- CONSTRAINT merchant_tips_tip_id_check CHECK ((length(tip_id) = 64))
-);
-
-
---
--- Name: TABLE merchant_tips; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_tips IS 'tips that have been authorized';
-
-
---
--- Name: COLUMN merchant_tips.reserve_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tips.reserve_serial IS 'Reserve from which this tip is funded';
-
-
---
--- Name: COLUMN merchant_tips.expiration; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tips.expiration IS 'by when does the client have to pick up the tip';
-
-
---
--- Name: COLUMN merchant_tips.amount_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tips.amount_val IS 'total transaction cost for all coins including withdraw fees';
-
-
---
--- Name: COLUMN merchant_tips.picked_up_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tips.picked_up_val IS 'Tip amount left to be picked up';
-
-
---
--- Name: merchant_tips_tip_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_tips_tip_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_tips_tip_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_tips_tip_serial_seq OWNED BY public.merchant_tips.tip_serial;
-
-
---
--- Name: merchant_transfer_signatures; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_transfer_signatures (
- credit_serial bigint NOT NULL,
- signkey_serial bigint NOT NULL,
- wire_fee_val bigint NOT NULL,
- wire_fee_frac integer NOT NULL,
- execution_time bigint NOT NULL,
- exchange_sig bytea NOT NULL,
- credit_amount_val bigint NOT NULL,
- credit_amount_frac integer NOT NULL,
- CONSTRAINT merchant_transfer_signatures_exchange_sig_check CHECK ((length(exchange_sig) = 64))
-);
-
-
---
--- Name: TABLE merchant_transfer_signatures; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_transfer_signatures IS 'table represents the main information returned from the /transfer request to the exchange.';
-
-
---
--- Name: COLUMN merchant_transfer_signatures.execution_time; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_transfer_signatures.execution_time IS 'Execution time as claimed by the exchange, roughly matches time seen by merchant';
-
-
---
--- Name: merchant_transfer_to_coin; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_transfer_to_coin (
- deposit_serial bigint NOT NULL,
- credit_serial bigint NOT NULL,
- offset_in_exchange_list bigint NOT NULL,
- exchange_deposit_value_val bigint NOT NULL,
- exchange_deposit_value_frac integer NOT NULL,
- exchange_deposit_fee_val bigint NOT NULL,
- exchange_deposit_fee_frac integer NOT NULL
-);
-
-
---
--- Name: TABLE merchant_transfer_to_coin; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_transfer_to_coin IS 'Mapping of (credit) transfers to (deposited) coins';
-
-
---
--- Name: COLUMN merchant_transfer_to_coin.exchange_deposit_value_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_transfer_to_coin.exchange_deposit_value_val IS 'Deposit value as claimed by the exchange, should match our values in merchant_deposits minus refunds';
-
-
---
--- Name: COLUMN merchant_transfer_to_coin.exchange_deposit_fee_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_transfer_to_coin.exchange_deposit_fee_val IS 'Deposit value as claimed by the exchange, should match our values in merchant_deposits';
-
-
---
--- Name: merchant_transfers; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_transfers (
- credit_serial bigint NOT NULL,
- exchange_url character varying NOT NULL,
- wtid bytea,
- credit_amount_val bigint NOT NULL,
- credit_amount_frac integer NOT NULL,
- account_serial bigint NOT NULL,
- verified boolean DEFAULT false NOT NULL,
- confirmed boolean DEFAULT false NOT NULL,
- CONSTRAINT merchant_transfers_wtid_check CHECK ((length(wtid) = 32))
-);
-
-
---
--- Name: TABLE merchant_transfers; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_transfers IS 'table represents the information provided by the (trusted) merchant about incoming wire transfers';
-
-
---
--- Name: COLUMN merchant_transfers.credit_amount_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_transfers.credit_amount_val IS 'actual value of the (aggregated) wire transfer, excluding the wire fee, according to the exchange';
-
-
---
--- Name: COLUMN merchant_transfers.verified; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_transfers.verified IS 'true once we got an acceptable response from the exchange for this transfer';
-
-
---
--- Name: COLUMN merchant_transfers.confirmed; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_transfers.confirmed IS 'true once the merchant confirmed that this transfer was received';
-
-
---
--- Name: merchant_transfers_credit_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_transfers_credit_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_transfers_credit_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_transfers_credit_serial_seq OWNED BY public.merchant_transfers.credit_serial;
-
-
---
--- 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,
- failed 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: COLUMN prewire.finished; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.prewire.finished IS 'set to TRUE once bank confirmed receiving the wire transfer request';
-
-
---
--- Name: COLUMN prewire.failed; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.prewire.failed IS 'set to TRUE if the bank responded with a non-transient failure to our transfer request';
-
-
---
--- Name: COLUMN prewire.buf; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.prewire.buf IS 'serialized data to send to the bank to execute the wire transfer';
-
-
---
--- 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,
- known_coin_id bigint 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,
- reserve_out_serial_id bigint 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.known_coin_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.recoup.known_coin_id IS 'Do not CASCADE ON DROP on the known_coin_id, as we may keep the coin alive!';
-
-
---
--- Name: COLUMN recoup.reserve_out_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.recoup.reserve_out_serial_id IS 'Identifies the h_blind_ev of the recouped coin.';
-
-
---
--- 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,
- known_coin_id bigint 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,
- rrc_serial bigint 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.known_coin_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.recoup_refresh.known_coin_id IS 'Do not CASCADE ON DROP on the known_coin_id, as we may keep the coin alive!';
-
-
---
--- Name: COLUMN recoup_refresh.rrc_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.recoup_refresh.rrc_serial IS 'Identifies the h_blind_ev of the recouped coin (as h_coin_ev).';
-
-
---
--- 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_known_coin_id bigint 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 (
- rrc_serial bigint NOT NULL,
- melt_serial_id bigint NOT NULL,
- freshcoin_index integer NOT NULL,
- link_sig bytea NOT NULL,
- denominations_serial bigint 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.rrc_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_revealed_coins.rrc_serial IS 'needed for exchange-auditor replication logic';
-
-
---
--- Name: COLUMN refresh_revealed_coins.melt_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_revealed_coins.melt_serial_id IS 'Identifies the refresh commitment (rc) of 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_revealed_coins_rrc_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.refresh_revealed_coins_rrc_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: refresh_revealed_coins_rrc_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.refresh_revealed_coins_rrc_serial_seq OWNED BY public.refresh_revealed_coins.rrc_serial;
-
-
---
--- Name: refresh_transfer_keys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.refresh_transfer_keys (
- rtc_serial bigint NOT NULL,
- melt_serial_id bigint 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.rtc_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_transfer_keys.rtc_serial IS 'needed for exchange-auditor replication logic';
-
-
---
--- Name: COLUMN refresh_transfer_keys.melt_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_transfer_keys.melt_serial_id IS 'Identifies the refresh commitment (rc) of the 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: refresh_transfer_keys_rtc_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.refresh_transfer_keys_rtc_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: refresh_transfer_keys_rtc_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.refresh_transfer_keys_rtc_serial_seq OWNED BY public.refresh_transfer_keys.rtc_serial;
-
-
---
--- Name: refunds; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.refunds (
- refund_serial_id bigint NOT NULL,
- deposit_serial_id bigint NOT NULL,
- merchant_sig bytea NOT NULL,
- rtransaction_id bigint NOT NULL,
- amount_with_fee_val bigint NOT NULL,
- amount_with_fee_frac integer NOT NULL,
- 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.deposit_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refunds.deposit_serial_id IS 'Identifies ONLY the merchant_pub, h_contract_terms and known_coin_id. Multiple deposits may match a refund, this only identifies one of them.';
-
-
---
--- 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_uuid bigint NOT NULL,
- reserve_pub bytea 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_uuid bigint NOT NULL,
- execution_date bigint NOT NULL,
- wtid bytea NOT NULL,
- wire_target_serial_id bigint 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: COLUMN reserves_close.wire_target_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.reserves_close.wire_target_serial_id IS 'Identifies the credited bank account (and KYC status). Note that closing does not depend on KYC.';
-
-
---
--- 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_uuid bigint NOT NULL,
- wire_reference bigint NOT NULL,
- credit_val bigint NOT NULL,
- credit_frac integer NOT NULL,
- wire_source_serial_id bigint 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: COLUMN reserves_in.wire_source_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.reserves_in.wire_source_serial_id IS 'Identifies the debited bank account and KYC status';
-
-
---
--- 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,
- denominations_serial bigint NOT NULL,
- denom_sig bytea NOT NULL,
- reserve_uuid bigint 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.denominations_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.reserves_out.denominations_serial 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: reserves_reserve_uuid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.reserves_reserve_uuid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: reserves_reserve_uuid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.reserves_reserve_uuid_seq OWNED BY public.reserves.reserve_uuid;
-
-
---
--- Name: revolving_work_shards; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE UNLOGGED TABLE public.revolving_work_shards (
- shard_serial_id bigint NOT NULL,
- last_attempt bigint NOT NULL,
- start_row integer NOT NULL,
- end_row integer NOT NULL,
- active boolean DEFAULT false NOT NULL,
- job_name character varying NOT NULL
-);
-
-
---
--- Name: TABLE revolving_work_shards; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.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"';
-
-
---
--- Name: COLUMN revolving_work_shards.shard_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.revolving_work_shards.shard_serial_id IS 'unique serial number identifying the shard';
-
-
---
--- Name: COLUMN revolving_work_shards.last_attempt; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.revolving_work_shards.last_attempt IS 'last time a worker attempted to work on the shard';
-
-
---
--- Name: COLUMN revolving_work_shards.start_row; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.revolving_work_shards.start_row IS 'row at which the shard scope starts, inclusive';
-
-
---
--- Name: COLUMN revolving_work_shards.end_row; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.revolving_work_shards.end_row IS 'row at which the shard scope ends, exclusive';
-
-
---
--- Name: COLUMN revolving_work_shards.active; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.revolving_work_shards.active IS 'set to TRUE when a worker is active on the shard';
-
-
---
--- Name: COLUMN revolving_work_shards.job_name; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.revolving_work_shards.job_name IS 'unique name of the job the workers on this shard are performing';
-
-
---
--- Name: revolving_work_shards_shard_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.revolving_work_shards_shard_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: revolving_work_shards_shard_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.revolving_work_shards_shard_serial_id_seq OWNED BY public.revolving_work_shards.shard_serial_id;
-
-
---
--- Name: signkey_revocations; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.signkey_revocations (
- signkey_revocations_serial_id bigint NOT NULL,
- esk_serial bigint NOT NULL,
- master_sig bytea NOT NULL,
- CONSTRAINT signkey_revocations_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE signkey_revocations; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.signkey_revocations IS 'remembering which online signing keys have been revoked';
-
-
---
--- Name: signkey_revocations_signkey_revocations_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.signkey_revocations_signkey_revocations_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: signkey_revocations_signkey_revocations_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.signkey_revocations_signkey_revocations_serial_id_seq OWNED BY public.signkey_revocations.signkey_revocations_serial_id;
-
-
---
--- Name: wire_accounts; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.wire_accounts (
- payto_uri character varying NOT NULL,
- master_sig bytea,
- is_active boolean NOT NULL,
- last_change bigint NOT NULL,
- CONSTRAINT wire_accounts_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE wire_accounts; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.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.';
-
-
---
--- Name: COLUMN wire_accounts.payto_uri; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_accounts.payto_uri IS 'payto URI (RFC 8905) with the bank account of the exchange.';
-
-
---
--- Name: COLUMN wire_accounts.master_sig; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_accounts.master_sig IS 'Signature of purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS';
-
-
---
--- Name: COLUMN wire_accounts.is_active; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_accounts.is_active IS 'true if we are currently supporting the use of this account.';
-
-
---
--- Name: COLUMN wire_accounts.last_change; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_accounts.last_change IS 'Latest time when active status changed. Used to detect replays of old messages.';
-
-
---
--- 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 NOT NULL,
- wire_out_off bigint NOT NULL
-);
-
-
---
--- 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_fee_serial bigint NOT NULL,
- 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: COLUMN wire_fee.wire_fee_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_fee.wire_fee_serial IS 'needed for exchange-auditor replication logic';
-
-
---
--- Name: wire_fee_wire_fee_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.wire_fee_wire_fee_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: wire_fee_wire_fee_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.wire_fee_wire_fee_serial_seq OWNED BY public.wire_fee.wire_fee_serial;
-
-
---
--- 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_serial_id bigint 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: COLUMN wire_out.wire_target_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_out.wire_target_serial_id IS 'Identifies the credited bank account and KYC status';
-
-
---
--- Name: COLUMN wire_out.exchange_account_section; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_out.exchange_account_section IS 'identifies the configuration section with the debit account of this payment';
-
-
---
--- 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: wire_targets; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.wire_targets (
- wire_target_serial_id bigint NOT NULL,
- h_payto bytea NOT NULL,
- payto_uri character varying NOT NULL,
- kyc_ok boolean DEFAULT false NOT NULL,
- external_id character varying,
- CONSTRAINT wire_targets_h_payto_check CHECK ((length(h_payto) = 64))
-);
-
-
---
--- Name: TABLE wire_targets; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.wire_targets IS 'All recipients of money via the exchange';
-
-
---
--- Name: COLUMN wire_targets.h_payto; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_targets.h_payto IS 'Unsalted hash of payto_uri';
-
-
---
--- Name: COLUMN wire_targets.payto_uri; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_targets.payto_uri IS 'Can be a regular bank account, or also be a URI identifying a reserve-account (for P2P payments)';
-
-
---
--- Name: COLUMN wire_targets.kyc_ok; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_targets.kyc_ok IS 'true if the KYC check was passed successfully';
-
-
---
--- Name: COLUMN wire_targets.external_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_targets.external_id IS 'Name of the user that was used for OAuth 2.0-based legitimization';
-
-
---
--- Name: wire_targets_wire_target_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.wire_targets_wire_target_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: wire_targets_wire_target_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.wire_targets_wire_target_serial_id_seq OWNED BY public.wire_targets.wire_target_serial_id;
-
-
---
--- Name: work_shards; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.work_shards (
- shard_serial_id bigint NOT NULL,
- last_attempt bigint NOT NULL,
- start_row bigint NOT NULL,
- end_row bigint NOT NULL,
- completed boolean DEFAULT false NOT NULL,
- job_name character varying NOT NULL
-);
-
-
---
--- Name: TABLE work_shards; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.work_shards IS 'coordinates work between multiple processes working on the same job';
-
-
---
--- Name: COLUMN work_shards.shard_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.work_shards.shard_serial_id IS 'unique serial number identifying the shard';
-
-
---
--- Name: COLUMN work_shards.last_attempt; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.work_shards.last_attempt IS 'last time a worker attempted to work on the shard';
-
-
---
--- Name: COLUMN work_shards.start_row; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.work_shards.start_row IS 'row at which the shard scope starts, inclusive';
-
-
---
--- Name: COLUMN work_shards.end_row; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.work_shards.end_row IS 'row at which the shard scope ends, exclusive';
-
-
---
--- Name: COLUMN work_shards.completed; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.work_shards.completed IS 'set to TRUE once the shard is finished by a worker';
-
-
---
--- Name: COLUMN work_shards.job_name; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.work_shards.job_name IS 'unique name of the job the workers on this shard are performing';
-
-
---
--- Name: work_shards_shard_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.work_shards_shard_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: work_shards_shard_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.work_shards_shard_serial_id_seq OWNED BY public.work_shards.shard_serial_id;
-
-
---
--- 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_denom_sigs auditor_denom_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denom_sigs ALTER COLUMN auditor_denom_serial SET DEFAULT nextval('public.auditor_denom_sigs_auditor_denom_serial_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: auditors auditor_uuid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditors ALTER COLUMN auditor_uuid SET DEFAULT nextval('public.auditors_auditor_uuid_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: denominations denominations_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denominations ALTER COLUMN denominations_serial SET DEFAULT nextval('public.denominations_denominations_serial_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: exchange_sign_keys esk_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.exchange_sign_keys ALTER COLUMN esk_serial SET DEFAULT nextval('public.exchange_sign_keys_esk_serial_seq'::regclass);
-
-
---
--- Name: extension_details extension_details_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.extension_details ALTER COLUMN extension_details_serial_id SET DEFAULT nextval('public.extension_details_extension_details_serial_id_seq'::regclass);
-
-
---
--- Name: known_coins known_coin_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.known_coins ALTER COLUMN known_coin_id SET DEFAULT nextval('public.known_coins_known_coin_id_seq'::regclass);
-
-
---
--- Name: merchant_accounts account_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_accounts ALTER COLUMN account_serial SET DEFAULT nextval('public.merchant_accounts_account_serial_seq'::regclass);
-
-
---
--- Name: merchant_deposits deposit_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits ALTER COLUMN deposit_serial SET DEFAULT nextval('public.merchant_deposits_deposit_serial_seq'::regclass);
-
-
---
--- Name: merchant_exchange_signing_keys signkey_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_exchange_signing_keys ALTER COLUMN signkey_serial SET DEFAULT nextval('public.merchant_exchange_signing_keys_signkey_serial_seq'::regclass);
-
-
---
--- Name: merchant_exchange_wire_fees wirefee_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_exchange_wire_fees ALTER COLUMN wirefee_serial SET DEFAULT nextval('public.merchant_exchange_wire_fees_wirefee_serial_seq'::regclass);
-
-
---
--- Name: merchant_instances merchant_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_instances ALTER COLUMN merchant_serial SET DEFAULT nextval('public.merchant_instances_merchant_serial_seq'::regclass);
-
-
---
--- Name: merchant_inventory product_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_inventory ALTER COLUMN product_serial SET DEFAULT nextval('public.merchant_inventory_product_serial_seq'::regclass);
-
-
---
--- Name: merchant_kyc kyc_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_kyc ALTER COLUMN kyc_serial_id SET DEFAULT nextval('public.merchant_kyc_kyc_serial_id_seq'::regclass);
-
-
---
--- Name: merchant_orders order_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_orders ALTER COLUMN order_serial SET DEFAULT nextval('public.merchant_orders_order_serial_seq'::regclass);
-
-
---
--- Name: merchant_refunds refund_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refunds ALTER COLUMN refund_serial SET DEFAULT nextval('public.merchant_refunds_refund_serial_seq'::regclass);
-
-
---
--- Name: merchant_tip_pickups pickup_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_pickups ALTER COLUMN pickup_serial SET DEFAULT nextval('public.merchant_tip_pickups_pickup_serial_seq'::regclass);
-
-
---
--- Name: merchant_tip_reserves reserve_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserves ALTER COLUMN reserve_serial SET DEFAULT nextval('public.merchant_tip_reserves_reserve_serial_seq'::regclass);
-
-
---
--- Name: merchant_tips tip_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tips ALTER COLUMN tip_serial SET DEFAULT nextval('public.merchant_tips_tip_serial_seq'::regclass);
-
-
---
--- Name: merchant_transfers credit_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfers ALTER COLUMN credit_serial SET DEFAULT nextval('public.merchant_transfers_credit_serial_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: refresh_revealed_coins rrc_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins ALTER COLUMN rrc_serial SET DEFAULT nextval('public.refresh_revealed_coins_rrc_serial_seq'::regclass);
-
-
---
--- Name: refresh_transfer_keys rtc_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_transfer_keys ALTER COLUMN rtc_serial SET DEFAULT nextval('public.refresh_transfer_keys_rtc_serial_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 reserve_uuid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves ALTER COLUMN reserve_uuid SET DEFAULT nextval('public.reserves_reserve_uuid_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: revolving_work_shards shard_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.revolving_work_shards ALTER COLUMN shard_serial_id SET DEFAULT nextval('public.revolving_work_shards_shard_serial_id_seq'::regclass);
-
-
---
--- Name: signkey_revocations signkey_revocations_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.signkey_revocations ALTER COLUMN signkey_revocations_serial_id SET DEFAULT nextval('public.signkey_revocations_signkey_revocations_serial_id_seq'::regclass);
-
-
---
--- Name: wire_fee wire_fee_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_fee ALTER COLUMN wire_fee_serial SET DEFAULT nextval('public.wire_fee_wire_fee_serial_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);
-
-
---
--- Name: wire_targets wire_target_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_targets ALTER COLUMN wire_target_serial_id SET DEFAULT nextval('public.wire_targets_wire_target_serial_id_seq'::regclass);
-
-
---
--- Name: work_shards shard_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.work_shards ALTER COLUMN shard_serial_id SET DEFAULT nextval('public.work_shards_shard_serial_id_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 2021-12-01 12:08:26.821188+01 dold {} {}
-merchant-0001 2021-12-01 12:08:27.343817+01 dold {} {}
-merchant-0002 2021-12-01 12:08:27.641319+01 dold {} {}
-merchant-0003 2021-12-01 12:08:27.652283+01 dold {} {}
-auditor-0001 2021-12-01 12:08:27.68794+01 dold {} {}
-\.
-
-
---
--- 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
-t 9 +TESTKUDOS:0 9
-f 10 +TESTKUDOS:0 10
-f 11 +TESTKUDOS:0 11
-f 12 +TESTKUDOS:90 12
-t 1 -TESTKUDOS:200 1
-f 13 +TESTKUDOS:82 13
-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 2021-12-01 12:08:37.853361+01 f fa27c9fd-6b8a-4a73-a420-a9c20c435f87 12 1
-2 TESTKUDOS:10 89JVAQPM740BFC7Q5NP3BGAK1J9ASAG4CCPET13JTSSP7JX9A8K0 2021-12-01 12:08:41.746972+01 f 32ad7113-0fe5-4322-9976-29146fe0caeb 2 12
-3 TESTKUDOS:100 Joining bonus 2021-12-01 12:08:50.064111+01 f 5b41a1eb-e222-4563-985b-534f758db2df 13 1
-4 TESTKUDOS:18 00TNSQS28VF2P5ACGRFV5TGYWBH5S7554T15VC9X0WZHVT0K2NH0 2021-12-01 12:08:50.762479+01 f 102e985e-f7de-4a68-8d06-dbee971e04a2 2 13
-\.
-
-
---
--- 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;
-7c639153-d45e-46b5-9212-411a0a5f871a TESTKUDOS:10 t t f 89JVAQPM740BFC7Q5NP3BGAK1J9ASAG4CCPET13JTSSP7JX9A8K0 2 12
-e3a4ee21-ea44-47ee-a1f4-34dc4be04713 TESTKUDOS:18 t t f 00TNSQS28VF2P5ACGRFV5TGYWBH5S7554T15VC9X0WZHVT0K2NH0 2 13
-\.
-
-
---
--- 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_denom_sigs; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_denom_sigs (auditor_denom_serial, auditor_uuid, denominations_serial, auditor_sig) FROM stdin;
-1 1 46 \\xc074de874a97e6da510f44a63fe43eef7bd3bb9284a1d29fbf346b052cb427f0d481e20a3a7a3447165db06fb5ddfae0df484593bdd18bc0192aaf5dcca9e006
-2 1 147 \\x9955203a82842400bc2b2431db96920bede1a4e42f96fe0aa0077fbcf5284e9709f6395a65fcac94f65d38dfcb02f968c27b674fd164e873979d437fc7eaf70e
-3 1 395 \\x88bb1ebc61ec29f15e6dea9ffa8a2cb4d626ec39ba5b838e449253907a0c5bd5e6acf460fa0fd0390b67ec87693740399bb372ec37218ae6833f954f82a59805
-4 1 197 \\x4bb239d99e9fa6e4cca9f1ab4bad03de98df4348f902046f617c7460cfad06e380f36d470524f622f2af81a36a219ce2a32865371d94758d2efba279a63bcf0f
-5 1 261 \\x501aaa0141c97a475743d5816c612ce8f659379ef88f628b9ad4e598e8a766fe4df1ccef00f4ca1fc537b29ebb14607f2e689e87e8eb54a735345317b8936909
-6 1 381 \\xd60ec734452b90329c7e94e7029f4565f9203f7bb5a552c7e42bc8d850191a24bfaeb04c4e5c236a07fa8336f58bde8ea82621aee8ad66b77e718b5b41482107
-7 1 146 \\x7489dd2f83bf9da47242ba192a1657cd712e645832e295759baa6d1979d0bb31a4a1c910791b2ef56b2397d1bfe652dbe7c96da6cdc61eba7ee37a33899cd10a
-8 1 231 \\x33b481db54d2d5aa78f476af43f73e0ccf52f55f333a9657cc1f042bd48888fb4675c64a430c3e1b54bf360c854d5c174573f4480bac61b0bb832be8baa2d408
-9 1 144 \\x32e07c7148371d65d6d1525c86fecb5bb0f55d2f096af56e3dd5b094e1e76e30d8ff9436fe0ffbab946de1e99b01f9fe80361b506cf096e73ed3f107c97be50b
-10 1 169 \\xbb0fcbd24fc7ab97d60b463031d566719691011d3d7ce509c51567a9b02568245688b89d84c91496c2c968ae5dd6fc7e96f0feef3eeef8c34a3e6cae0f8c270b
-11 1 380 \\x435301bcfaf676b8adffb60f0a66024f364ecf14e94d4401841972e1813c78394f7b73270bdff378d9f8a1bb807f946870de58123461098418ec864c4f5bde0e
-12 1 80 \\x93556403518c6e7926d644bacca6b437b5ba7c5477d1f6540ee1648efc28e6159a768a0b63ae1161b50956362a1170e6da9dafa5ec5317d087db39bab2283b05
-13 1 362 \\x8adcc6dadc800a47937e520e73bcfc427f4692cc8684058649c26b68422b2b71b9424f4e6b735704fe2a850af1724e2361123a7bdb0b6d2ca1e5aeccfafbd50e
-14 1 34 \\x9d7d50110d3167ceb621b653f9176e1be868bf97d8cd1de59d23623bc32dcaed664a3ba42b62e3017442a942f9fd282e1471fab3de1c6dc4810e6bf16d38f706
-15 1 39 \\xc20374f718f2b2dd838b5efba84a9b316d43d5aecdfbf95e94064e865feaad8bf1895d303601c2d42d56c75b5b53bfa72c5dfff80b377009d01393eeaca16b01
-16 1 355 \\x6a399968fd73c99471b14a50758af77fefb31e84770ccfa522f82d9ad4c81304ae4e504e531236d416392d547fed648d273f7ae94d2b4584b54e1ba53a727601
-17 1 348 \\xbb919777498f3150f30c93f38dad4fdb85b99542344385a594eee2f32581c2eb95d6c2b3e85894bb7195978c1090d1fb099ae4d7e0fa414ab4f40b5eb70d4b00
-18 1 242 \\x51566f0ae4aaacfa92ae4c9a6048fa1e6e1fbaff187f86ed43334c0a869e9776a4efe4014c4aaf5787f366b1d21ed695e5fbb9ef01c3a4edbc630578edf88801
-19 1 213 \\x860a9ebbfc3b289055c7d8124ca46fb012537b4fa5a4078813be6026adbf350d10e68b64a81d7bf783679bfdcbf8ec60015a847f2957602eabae3859468b9704
-20 1 243 \\x438ff81f5f803edf9e5ce34f3ce0fe3a0ed3f1fe0512fab7f0861f2dc57688a59125784e363e50a7a6d796604b7baf4d3ce7190a182051ded35c24917aca2808
-21 1 264 \\x80b132395f5696342c33da67d7975b66fb8bb6e8fa4ce3bf2b69eec790e1bbe7aa8b6eef1c6b57e7f830efcabfe650f325bfc1c344fcf41b4aca247698c5900e
-22 1 153 \\x65c5471413fd8fbdc53e24a40eb378fedfbb61ce32f7a641862258dcabdfa0546846fc001823d794e07132086853d70c5507c2c5adbc2b7dced59f977355b709
-23 1 382 \\xcd15a147cef28c86f9a2e25174b32442e0ab6dccde2d34f58efa9c5ff6068bc23758b2aba2db12f62d1a3dbb1b648b07e11c1952d0c8a8b3177ecbd7364eca0f
-24 1 389 \\x9a5cba279ff4107f52e63ca7c0329b6437ced5d09510963b0bc4287e3eae4c5fff69fb1708bd3b33c859ec026d9d6f6ea36387a7683d5aef79eeb107e7b8450e
-25 1 68 \\xd775307eaad78ea6f87fb87871ea9baf60371eefa8950c07431aea543e9a6f0b3af29cc679eac07e23307f5d35ef145de568262c7dcb6e502a826fe4f1873c05
-26 1 5 \\xc37086f7f3838fb295f7a40e12b4bd01aa5e2cb698f5e28f5b9bb638e242016c0bff85f4110027ad1d3022dcf018d2d2d586c651650a68709601d852ab17b00b
-27 1 20 \\xb3a95fc4b7678912c5c82b9975760ff014bf55573b2e9e3dba6437a49979b895ef72b3de59a9990b2ae8ea3749e2748ae6ff624b8543160ff21df0151acd6b08
-28 1 358 \\x78007eaa101bf164db2e9710644fc5de7938472a4a1993abfc024f779c9cbbc925ba851aa6e6feddec46bb001ee551288475214faf34e84ee828ace4a4e61e01
-29 1 22 \\xa1086ee861e329a96d932e921857b06fbf307f6383478b031604286d95ee31a10e1fc553a3ff282df10a9c53b1fe8e7734380d31d35de141744b055db633b20b
-30 1 207 \\x7bca098456b88dfa64bf9924b023f77f02150734d03bfda86de6e2ca7cf1b1b179bc5cb62e4e41540639e021f652ea085a4a159b936d95b6a1a518d1a2b48d00
-31 1 184 \\x14828242f3dbfcf48e8001d848233ad485d3dd7f814f4184bc4d1708129cc8f9ca86f0b1baa58bbe7ecc25b5cbe87c64b6fb06110ff17838de41edf047866107
-32 1 100 \\x409537254fd40a75d6edc5ecc5d1c215bdc26aac8e21f39897f9186d0e672aa4a06238a7638002bd966a5a6828a810bff6f9ccae8c85da19e4e5a192b0fa4607
-33 1 164 \\xec0ef3b7074c8a9e81b9404b0b89d5da931151dc189da556e5909671c52ea935c80eb21d1450bb664c5b6baf1c51079a09c2f93798b972afe98e7ae53e969a0a
-34 1 276 \\x2b63e017194e1e45ff8775a9432870772775f6f5d8785e2a1bac85511ac9c150db7a37557e8557b387c16d38b4b26bf61b85be636b1b44ee2d7be18fa1ea4b0a
-35 1 354 \\xc4649f87c3c3b7626b22b7a34f8302ab33de6cbb5cd3a84c74468b362108fc8ea5d32399119c07f14974b63705e7efac872d21269a458ebf1360d0d0b3de780f
-36 1 275 \\x0e033f9648e7e9d4c504700562f22be2f935404a0b67fcbaf8b26368db56a0dfd21f49710b29dc823f9fafc99a7f7b1b246aee82c8756de1aab448f97f09200b
-37 1 244 \\xdbc36806352a67be8fba48b29c030ac00b4cd132058fe382c79aa4ffcb1c3dc1c81cdcaea4ccb87b6764293c32f4e1d95f683942ac572d468c67a85fd3d27002
-38 1 407 \\x0711fd01af0e805fcd0a056b104ba123a146964c43add97fe19e957bfc816d82a834553520051794289f7a7cb9cf4f413fd4c755365f2f14d7b4cdb967eb7801
-39 1 222 \\x3e7d71cc70df93733d94a761dd517d3f208ff41c41294822c44be9a32dd3e19571b21afc5e3adccca7b037903d186b1113501904a519f0c00efb6e4179a4ba0f
-40 1 135 \\x3cab64bb04ee0ee399a269c7fcb41a26d5b6f6431a58bdb3f208ae8bf19fb6bd6e3f1dd1b16bfe8fbff883194e88b41718d51bb614ccbc32dcada8e2cdb52705
-41 1 131 \\xdbcc2671fe8a317e5a5a6550bafb20fedd89905ab6490f5a1bba9ffd214d23cf1665ea2df8f89062a26f63191e2c3d9bec3693fd2fe8bf71952ffcc2f8f2e20d
-42 1 66 \\x09a4f2b5ba715f34ec5d0f96c3d6070f572a134ad0b4a4044ab5a2cb428b71f075cbc10faafbe34c66d0d56d81f9ce402a81dc3f6ba50a6afb8deef271d1b10a
-43 1 293 \\x137ecfbb3e00c323331ac84051190f4579e58f893320f8828777e5c4a8308c57ea2ae72c27364a5e280af347b69c4d27aef5c9717fced7f0d82854c894e3cb08
-44 1 43 \\xf4f65a8342f4e70dac1241763b96b0d179f67e5c7fcf1bee7e1bba8b64003a5f858efde5f7df218785fb5443a510dd825b85af24d914236da33f53388f6c0c09
-45 1 299 \\x912413b2730bb5c01188504a4e099a0aa1c3a9d69977903ffbfdf365fa3b801aa6a87b021d0e062133811f4fc759b0f351f77c05fb2b15ee28e3d24c460ee60d
-46 1 138 \\x6a31e05f2bedf72a4559825926572af8c083ee68c966a4b30c5a676280876ba0d4864d44c4d2d0dff1723208f81ec3dbba8c53398237f32916bd258d43084500
-47 1 422 \\xbb11c24b5d67d26b569bdea7cb771d88ceaec174b0ea6860e6f566ce301c16af1f22aecf49467ec64a214a480b1a4706565d56533bcb253e4eb5bb885d00bd07
-48 1 235 \\x89c3898414eae1bc811de44bca8854ef86de76b6567c665867553c1657b9e75bb96126794082de9ec144e8dec5ac71768859b6bf87e59fc64e9cc157e3f02c0c
-49 1 418 \\xfec6039162180104f1760b62c6c5790500c7a7ced161845e65a47d19b17a5fbaab2204b7dcdd2c967b2a794164fec6f1cbd1c3a0746789fe748e1efc7853aa09
-50 1 234 \\x224f3fa2667f19143f3c25972a03bd092ab90796b9e6601e24bd1478f41fdd2ad3296d3b526ef1f16e8dbec40e63a713dc7398e00d35afcf282da1fb64a17e0a
-51 1 250 \\xe3cff870858e28b6ada565436de54049edcde29cc59123104c99da2620c38aae24ce4f6d5789fad9a153dd46884277550c3feac784c664fbbfc7fb8bebcb0207
-52 1 110 \\x50932b6ada5c4a3f46d9e3456abaeda08f5c519d85cba7a566c7c36d299001384b53e3d3d755c7e797398b5d5f35699661ec43c2e8205022d222149b90471306
-53 1 70 \\x3b4568d77bedc4083581596d1e12e2aa80c4c7807463372bb0abb5776ee52d99a11ae9db0f0672110ae03e944031e74c0d0e44297f8b2bc94a641f29ec7f8102
-54 1 159 \\x62f8e0c9c07a2b89d1b27e040e36c38e73f9ecb585b26d4ec421fb050eab51727b3c3a179e85f63c3a359b56b94cb538c4bd12b961b3fe8d6c4dcd6c9db51803
-55 1 7 \\xcd9e33bac91d40b7494a335088fa786d03f184832fd08b6c743a7816fcbe303db1ea7ee1945069329246034d2876c8a15ce9da1b4286421e8a8361688adebf04
-56 1 23 \\x7831be33ff270eb5651a033527a0af62f324e6341ba90d0ec2a7204c81b842724e454218d237d673e85103ce9100027860abdda657ed6d2663f8b803515f6606
-57 1 44 \\x450be824454fbade387aa5c81bc6a943ab34266dfc10b05c6ce22e890497112bd61c2d168ff0658ce16ce6cf619e16d5d26a58c0d563c3bb667616603c79b306
-58 1 340 \\x4d12a9146c9fea7796959d51fe34c021aec871310cfa052a06c491c78080e07e1aec868d6b053895869a2a88839d11cacc694b98911240c4cf6cc3f9b0dc2202
-59 1 88 \\xff4275072e4c565256ee8acdc04aaee4bd6cd27988d477ff120182e6151defd3f1396b20e833f800fc02d99e02682bb659abbe20f390d69f793d93bd183da30b
-60 1 69 \\x47b4b8410a9f849f4afe98461fecedea234fc09de7236c440880a70b22306b91195d02a18088c928c75eb679d0e080763234fda7a540771bf9566ec465773f0b
-61 1 247 \\xaefdb4c4d9f50f3d87f41e1a2880eb0a830bd77b6a70e95f673e1f52136db84e080f3d32d4062e7abcfe4d129d0d6127c457abeb9937c014ddeeb5e4427fdb03
-62 1 77 \\x11f59bec8cc389e3bf0645fc6a1c874fa96ddb851d8c1ce9337b0b531b2d4105b2281a3f0231a59cb96b89dc3098bc4ea13cc04eed3bfa393697e2b3e6dd2b0a
-63 1 49 \\x5a020208cf6082b3df2190ee44787369208db5d9841ed163de4ed55a3aa0903c795e8882d4c596a04465ca89668e148e8185ded9d375e12cc2e69c8de8f68a0a
-64 1 55 \\x6697643982a0fd39e6608b23f48ab14111d2bc3a9d07ff7c22c778d3068012d5bc71f50956c6875c70093d53d9427c5071c62c82ecce937ec05cfb98a7f46d05
-65 1 408 \\x0a9cdfe97346d35cf209cf423ddf3df6eed4482e806e59c2d8a738c93babe2c9c9420a3ee69ed53b4bbf1995388589bf3604a04e93aca244a7faea196bdeaf04
-66 1 170 \\xc770569c03e1b757305bc78d4b1403c467d833bd2d4711267ae78bb95626d6370fb1692868312157f89b1be4e391db8af558260ca8ac13916ba032c26fdb7e09
-67 1 424 \\xf03fbe51a4fe60c7d0c785af40dbb48360c12ec242864272fd532e960dfe7a63950c07f96aecd258a2c21fb9906f2252a4c5fe46dc7917f69e58b440b67c4c0e
-68 1 325 \\x852d04aaaced4e0de31c2b5eca3b52d022ca65a83ea52bcd69c7ce630f8db5e1339c17c1aa64e0b5373d784d920607ca2618f707fdda72a49a515f528b910d02
-69 1 174 \\xfb5c71ba90583742aa88141ab13cfbd22e79d7b6e4ff22fbfcef202a896cea8e06a9c007f45916a096b58ce9b282a7b970573e184b874192d5179ecdc325820d
-70 1 166 \\x10d7c106629bf0a8da213f1a82d125b67fe2fb2d756c4e7b7a239b7661841dd9fad64298da53d0ae3a18dbeb329dc0f0fab910a7631717ad30e79fdc7128c60e
-71 1 133 \\x8cef0a119ba393d7f3a94aeb8ec93f504d14a65434c64a102da9d1396621889a2dc9d321adddd6ee1c50c11a62e0adfb6adc2aed144c6d3f6aefc6bc8d449a09
-72 1 338 \\x79d312069ae475d90d0ae983e1f92d76a396e22b9d97b3d8c34b02fec192d479af1b03c3d715f792763ddc271aa44f36c0d7edd061579c683621171e01db080c
-73 1 85 \\x85addc0eac2b339c14e2ffda66d06ff8629813768015184eaeec5ac0f28517dce56b35a6d17694e268d616d181cbfcb0f3a8de06c1bab72a84a3246ebc030108
-74 1 248 \\x42a8d321e6e413b21fb324d7a26211abd9bc28c2bfda6ae3cf464d6fae5b883311de7948346f48f6133ef4dc6902c1ba90cbe06a5c5b920c769eb4343384d700
-75 1 314 \\x65fb7c5429d0db1a8116439c7cd28f0fde95b8ff9394b8943f796e25287acb1aa9bca0d74ed4b4eb9471ffdaa37fffa4c0171f0d82fabb3500cbc75ac9c11a05
-76 1 94 \\x8e283e3f672224218deab60800da9540b308e2a1c571fb281f2ec6ce121326e6404c3c333650e087e4a0f940b5c2793a0c073e1926bc77345ea5ba012d30cf02
-77 1 109 \\xfa87e468f2fc28f2934eeb8b27fea2679dc629cdd2f86fc3c86c7b6756818e4a3e9e1509add81ddfac79fdd4c50c53cbae310d4f2e16d1b44bb79c7d1de7380a
-78 1 266 \\xbaa1d34f44471ff2e892b57b3659309af26850aa7620a6b688eab3a3492b09abc53b11009dbd9dc138a52406fdd11676526494f53dc86c5028a6c4a62b95de06
-79 1 363 \\xbb82bba281f487c1a3ff44154b5f3c0c05e845ba957216b228515cbeee4ae4da9348ab3d41f7d9c31fc41abf0dc5101954ee1e595632563d2bf17074e6334f01
-80 1 281 \\x08e76c3fad8dc312d27a31c45cf39bf409545fcc92216f8d46b8b94eca36081ba6daabd379bd34dcbb285e5cf61beb57137a591abf2b97973d626618bc016b08
-81 1 258 \\x1bb4b4cb2b1e1ae5f040b20bf6a4abe8aa393b8462d80acde244aa181c1998262c3a89e6af00180b972cb056c6a7e21562b98e503f67e33afda1c3a1c230f705
-82 1 142 \\xc8290db4e253786144ff690d92a02888e95cc8d8f7f2a4e0a382d558b0647d823b4119e356f677ceaad8a3022a88b137fed35a35f04d19c8ed7f6ef578cb4e0c
-83 1 208 \\x0cd9cc4130de6dbf44d85c6daeb363144daffd9afcab863d15a017c618ea382bd4533f50cce2df29ff7ec51fd32ffa6933ecaf5ed83ba29e8cd3726bb26c8303
-84 1 60 \\xde386e9aaa5d786f01a72f640ec8ab415f35693807b86210a8f4e1f9deeb88dad4bf174613ffb758a0da6460e11aa60c464d272adbb001c4850998e835e2fd00
-85 1 126 \\xeb293e42a8781cd5ee0ab59ec352f7333d5819824da56a3e18f55d76e989d5db155e44940f0e69e9a3ad89b39fbd1331a8a24af955ce6dfb686d26c1422dae03
-86 1 297 \\xe7a8220f27ccb32003cb8f1e571872094f768d96223fc8f9782dbb18934586be17e6961af6469aaf27e63609ed00319c7e3d9f0176562bfd06ea07f881d89507
-87 1 127 \\x5d13d200b6b7b970fa7b89a607f6118b1a1b87f80f48469c9c0378a35c160e35d75d4d48f0459c8b2bf167d513a7746af5aafc86b4fee94492e460cc13050304
-88 1 241 \\x3b61fdb9952496911f4b4c108d8c0377783b28c589247b76ffa3d5f9ad3ce6d9f99abd593389a2fc93f7c1c94859c87063a80ca12f5b98c2daaefd989637840c
-89 1 390 \\x5baf9104b46daeee303b5ae7d28dabcef60873675482c7d7d41b006b59aab0cbd46abb69bf2714c3b3660adc52f665c42bccb16e458f752e45eb26bb6f90f208
-90 1 130 \\x556570857287c29ceb0d7b141b51b47c512d69728a11cea0330119f9a9a68d03f7372cbac1e4ac11d72dd2a52c9f7cbbf38e80e29b54a538b96ab92452951101
-91 1 284 \\x3a8658e15ba52967e946d97109961903a8ecb5f509fc7ac22eeafa5a9e1f1e8c251fb8d0cb8835a74fb3b93f958ad9394a9cdc62c4b379f3f4711ac51cc01c06
-92 1 178 \\x595d48a50e87965314e13328f4a72e47e931c88239f20394bf6b1df5f1a7236adc8a994ddb88fcdf694c3b2601025df8ea77180cec70c3fd56f0426a3d7c5207
-93 1 111 \\x3b7e5b82ebac0400792b0b76e8858cdb44b09ca24141b527e6b47e445f5c03dbd54d1e2d4627a3cbe1c9f063e41f5978656cb104f310b6da9250eefae5fe6103
-94 1 185 \\xc2f60e39389a152566da4267ae4294d907b0c4607c8787b2ca42055c73c59a0ddbb6110018fc76c821bbd452d1583acc6a79dcaa8950c5d2f55365b70053da03
-95 1 119 \\x7a38630bac15ed9408a6aefd2c5691c2b0d23b4a904bb52e399f7faf91a7155b79d2ba82de907d0e76721644f1c7c2c30d392019f3639d4b5dd95feca5b48a04
-96 1 307 \\xd07cc4c57f90253c13b875d3235cc93a9033e2a094785bc3fc40c057b6c99ea57d2b86531a5d3e0147b3e3e23d457fd1cdb932f68c10b620a1ba8b24e246d602
-97 1 345 \\x8582aa6726de25dba631c5a57f46e56a99bd0b3bcda7e7872374f09f6657d7c8abb1107fa3afedfdede5de35e63acf656d0dbab13189ea8342fc00506d37fa0f
-98 1 342 \\x8a5305a6778c386d376bfaacef0d8c2c7fec135e53a6232e1e32f319aa9404f81d45682467f69b02a5beb73ffeb7ce5f59ea10f1f20aff23b0a9ebe8d92b2809
-99 1 226 \\x383eb2ac2fc6ebb44e6d74da119f28024cce22abe5278fe6e14867d4661c80b18e0e6dd5b66f1c592ceaeaee6aec60dbcab42eae2e2e9a2ebdacfa6d9d1ca402
-100 1 403 \\xb81cf6b79b628a2589c93f2a20c3ca7a56af058ae9e78fee851a10b2c40c2c6dc4a81611e92ec857d7d394dde784b21080165bc80ec4a72540ff1b1fd9751207
-101 1 417 \\x8c33e52ddbe9f6005736a106aec46b3ba081c59a01ef90c7ba4a728d4d3d311f001f81be992eb2be48259ff83189b2a52064d33119b291626399142e0614ee03
-102 1 154 \\xf02f8121af48e2cf2978596ac13626ec6a3c7b4489d45362b499dc03ae189eda9ff3ee5b79922b6e99210c5e3f6d83e202bd66480bfb9d7cf22796a84dec670b
-103 1 58 \\xffda8423da3f20164be52f6a8b607c4768702c0ca37978da64c412711571057dbfeabce2a2b221b24bccd29d77e184d193541e91486a15e3c92c7a7c606ffb00
-104 1 188 \\xf954b4851c17a6180502a895e0fbf1856aa7d036c4b8c1b9bea7ede54e5514a6f8c4d0faa7783f2373491e811f64c8020ae2788634482dbd96c76f16f8044c0d
-105 1 399 \\x8ec72b45a4981ce79c999f975fb7d45f858b1b455bc09563ec3b6d68f21bb6c58b004c6b63ef4a3daf52221bdd25ac3dea7c3acfa9c0f2c0aaccd5da4a6a3e07
-106 1 183 \\x62691b6ad2b306b6a4a76d692436f960dae16b3eaa081eeebb5e4a85fb5f5769f416572489fedd05e14ddbaad5ebbdd1eeb63ca7ce558292501d8a4eb1360a0b
-107 1 216 \\xff5e24f460339eb6f9c36d505acb79b78a2dc38523f87030e28409e9588745c47461c290889ec41c277bfae1743bd0c10ac9487663bb52c1c73492f78eba4000
-108 1 406 \\xf40f4d0dced1931805ed39a948b5f1b30f2828df034b846981bdf4e0787b00855b889c2140c61bf7ee275fc1b1c7c63a7941695a149f85b5f7aa08a753f23508
-109 1 199 \\xb2013eda749021420c600886306f3e4850989599cedde2f5a5d23f2a8ade3f580186d02111d98c51528e8fb5e15282ec41788ff66ad68b83e81eff03fd97ac05
-110 1 52 \\x01320a094d4d6bde8d7be813a2ac14d1f414802e8bb8b9145bf5abde24682b58f49b79018e0183f8573777c6417acfb48c79fece495840726816260fd27bea0a
-111 1 349 \\x3ea29677be4387ca4829b74cc8a996333b0f106dcdf5ef04a83737e8765c8d1dcf6b65d85ca88260493ca51c989ff7eb77d17ced9a9f5455ab28890aa4e46e0a
-112 1 272 \\xccdc8fc65cb5aed9776720425340649dd0b33283505d28f7ca17346f4a8691a38f272ff34940c6c4c96ebe5c41ae54d8eac382beb5153b085983895c9ea6160e
-113 1 278 \\xb736d3d60e84317a62c58400795594ef7aa36ad6724cf3613ee7870c4bf760cb66e60e0efa8cd0297809d97a156f57060d6336af9643cf2f5e331d818e75b504
-114 1 356 \\x39b226b626c42dab0b70ccc31e2be3ce9377a06c10c8860799b295d8f6e99e1abf3932dce147a0d49a0cbbc7f1de54c3a406fc7c68dfa3c90217b6c45433d303
-115 1 310 \\x3b7eb8041746f9c715a3a9c3669ee4e48561cd52865da33a316da6533c0cdc304553fd2f9012c6e72c45d8144230658d17336e7148195ce258d9ec342b8cc605
-116 1 140 \\x17133cf226b5fb6686b12b17453211fe8b15801f7f529ba4bd292a2ca7ed334296874764231d6df66a7d088d0ec6fdb93ff78b0c573b2b3277797ae073dfbd00
-117 1 182 \\xaaebd22ef74047355203192b757e4d9ba164d3d878b0e046e259bbf2af32b176cacd30cbf2c4e718968f193cd2e5820c3b8d6bc375524aec8661b9d0ac41fc00
-118 1 186 \\x6510ba3fc965f1bff228f6b8002370f37dd184fe5798895951cac210a760aac11fdaa824163ed17c41e401613f5b73d4b52cc81d6a6c6858a76dbf772cfedc03
-119 1 14 \\x218e5c4f7f4f5ab6d7d017c2d2648e735de85ac7690ada5dbcd37dac6baa84193f850bf9a660c67c59929047a2a835e593f7838e1df6a1c3499d7c4dc8a76b01
-120 1 209 \\x7934862c301537a35b3caad6e88a41706964db6293e384002790ffd249d1142223bccd50f77999247aa75546cab6d45dd9c896a8761d3705f33660f26b5aa605
-121 1 329 \\x7037064360551c6ef88eacb0c324ce456abd4cbbb90959af5bf45458f7a626f94d1aa396fb191b1cf914beef577ebb488e9e407479ae5c59ff3714445b72bf00
-122 1 229 \\xb8392a4feba7c07ae81f0d8c5df8fd55452aeda51fb706c94ab86ed0830a876f64f4a72401a6972160da14cae0d608065d48382ca6ac156d20f049d2138ea407
-123 1 81 \\xb138c7c6f2a3bc1e152d974f9555d73e43e1ba43d8e8add800266bd10a1072a48c20c86652d56c2eef772ea1799213b33af376e4040d2cee21283dd3de27e00a
-124 1 384 \\x15275ea74605a60624d53f5b5c2a399f4275d9c198ca8250591b40de568c14de1afdf08f9d1afa9ea77f26d5b0f60013327ffaa33f2e96432625250a2439120e
-125 1 92 \\x50d08c47ef4cd25220bb9cd6277643766bec72a539168a3a478ed0f9e54f5db0d42af7e2748e3bd4110841c37cb7a381d429cfea050a8fcf319a22f84185fa0b
-126 1 176 \\x77a9fdf79472d75dc7cbf55716879d91bb1bfaa24f2261b218abe15cdc087fef5c378ce5cbf8f81fd46d30ebc85bdd73e061171f45f0bca1058d6d766bdc1a02
-127 1 343 \\x86493a76ecfd7caa62aadd58761d32170e03b9ad8090331f812d0e33f86eef29ec4f7df78b2c5ea7350a610ba330c7723ebbdb6f55fbbde8cd0eab382dd7ac03
-128 1 401 \\x7fb41ed603c731264fc7b2a85f17bace62b5186bf59221ca159a9e36c816ddce5669c012b06cd6eb717d12478cf567f7786b938206221b460f0c1f97d77de60f
-129 1 254 \\x4fe58470a96ca1e58a40eb935585b203d847e0d76346dbb1bd74ed319b40a357144b65c7a43947afc3b7c7c8450c45cfb7bc304b3cf85cc3e173f52a69a28300
-130 1 187 \\x2b0f38dfad897fa2df7cf8dca9f27eae6d3e2fa3140ebf58eddd44dae4a8dd6f0bb5435aa4efe6b07b50ae2533459e852d351b6845e06a0d0d5f46f776155d0d
-131 1 288 \\x27eb997648fe0ddca8547e22a2d96fec04088cecade58312d2b1dbd54a5fef52ddc62b614a82769613e216cb1ee7e8366dd4fa377ad29b793687cdc33cef8107
-132 1 333 \\xbe0b24124bc4dab023562816dffd002ce42d0db8d573ec12f30ed446f1ecf080ddd4c1ba76ae7760a081584b8a9e1d75820d3f05afb0493a11cada67a7e8a307
-133 1 367 \\xd752c8e68bc01fc1e960a0a4f262729cee427ee36b581deaf20cb4eceefcf896f7a1e211369f07c4e6bd9499669ef1c158492734161b52fd870083a60ec52a09
-134 1 370 \\x14a81688ac8710aa49187dfe334893b4fbafb1932220b07ab94606de47b415cfb2a7ed4f3a380eed31ce904eac42a37bc12d86b3abe5b88475a90ba54a304d0a
-135 1 217 \\xd5880bd0a916f21b4302455b3daae05ce8e987411def6c46fd13afeb3aa5b8ca7e0d63c3939d639d474813cd3ea927cb6b866c37a36476efbb2ed865ed29c209
-136 1 121 \\x6baeb0eb2140b7651d3087b34f218597609d48fc8c36ea0c77f3ae4b46cb4871990edfe8ab71a8c5615ce9d65f0f58b3ebb238f04d4c781c35344932603f4809
-137 1 347 \\x5544c7da14efe11b7f2ba2fd81d1fb7b9b612a25deef396a724262587c1b6ffee6b98b5cdd54b78c9661e714e567f5852832928a427ae71f580af004a2112505
-138 1 129 \\xaba065f2cf19b7d9da79ada2083c722b59bb9827214cb0d65ac4da0a6441ee037c8afb2e3aa8c8e466fcf81cd7d21a2de77b8ec8d4480c3b128d589d4c21920c
-139 1 283 \\xdb05e88cbcd9cf845d24a07399df560a3794c2a670dc474adbd34e4af2573afff5a8bfae6149967deb25fee6af8cb5be7dfe263b719218784fcf57f969601a09
-140 1 339 \\x53b4f5e274f8ca46ee5020328dda5fbf33e511b422a1d3e727ac0f60e28c51b296a2991e6cc99ff7be262fcb69d6ce20148dd7ad1fc9450dcd62b4cc8f8bc70a
-141 1 201 \\x9a6a5dc41c59528f3121152be6c7afab324a6ca154389c033cbaad87d8ac92907f058d112854245a0302eff4c9e90036992c01fab2a6d9b95234e0f749d51204
-142 1 265 \\x96d9b845bc74c20dcfb2f3a73a5fd039246696dceaf579322923b9ee4ff92cdc15ffd00946061b180bbb08a53d076e8bffcc2f568da248f5c9dd659a8d7b8b0f
-143 1 271 \\x2236462f3af0a8452e8634b1a9aed7a56c023cb5487f40e7d040b8dd921fb44266c422f783052e86a096646125bbc6e007844e4aec5538afcf5eb1bfe6d06f0e
-144 1 141 \\x8d3392e887f3b53f2db9f6b38849fc57cf0574fab7a1e652f55b824a907977cbbf23038eb421471778b923b3977b16227c7c891eafa454ec47347e858e914d03
-145 1 124 \\x31b20b02f0527b1460555a638911a23ef1bc11cb38e14aeb3a963bf845f8dc2c80ca7ec82a134ebebef9290b880e5a01868429420e4d02c934187e38a630590b
-146 1 190 \\xad2ce7fa514121469fa3ba26f34bf5e3e89f49c4f2af3b0a85ccdf571a0dd4c5a73e8af90a2a3916ee064105b0a527da374bcb9d287161cc9085919cb6466b06
-147 1 76 \\x14714b71734f8cf6bde7c9a069b92ad692bd73364d82ea2a159d833c834dbdedaa192eaf57a62185c207afa6a4eca85e1f49b6cb345148517ecd011d1891ea06
-148 1 1 \\x9cfffe417740092fa8145a63f64307ef7acc8a2b32630af2ba2820e9ac13b60c7c5fd96e71ccbf9ee393398f296f5ec6abd732a1b21a1614cbd4610b06784808
-149 1 232 \\xbd49f54c2027357bb72a1a13da39ee9052190a51fa4da4ac0909aee453a7427b8722b2fd90cdc59b0cd2020660079609d9204d031f87e4a99911ef12d0e2800a
-150 1 305 \\xcaec985bff0b48663782a30da1c7d71dcf975d6d74d9cb5df3a840c05e4361fb4c5a9a320d8eb6284e09e87ff1b9ae8f641bcbd744d0ffdb7bfc5c59b3e1ec03
-151 1 378 \\xc92879607a7b8d053ba29eb36b75671388af378731e969b53e6bb3c8d2ff1edadcc91bddb1b005e19ea3a7a3e54f8a40862a888942aaa1ad15572ae16518f70b
-152 1 50 \\xf689f474fa0fdf493cb00ce3e977541d97e4766669344d9bebcb6380b838330679476d1242a1f020f536a485e59c86b0edecef6360fc888748562796dcea0108
-153 1 365 \\x8cfc0ea7c8a76f05e51e824bf38f1a857719a2d141f3783703937153ba4b745513c49a59c2705a05275bf1f2b4d4d282a938833bb1db57dd5a579266359f8e06
-154 1 360 \\x143b2a89b52f4b8f77ba7b76ff7f9a759b702cca9fd8691d80f1be73cf3377e902f36acc6988a039a8d9fa738b5a847e306116aeb9e7b932fddf6e7b2ff3f108
-155 1 120 \\xc0061c6312560b766e73bf78c0b32b232cf0924ad335ede0539853521d751e0e97328ce61578401412f614aee8df26e6ca6ce81f4426d01254876d9d7f22c004
-156 1 273 \\xbde43cc3a3b5b18e89d704cdd477f2daf03cf79c7d0e41300466e8b45ab915ed0636f3ff86e23607aa3d09e7eb1f2588041ea964017dbad7c186281cb9eea904
-157 1 328 \\x00415a426cad2f007092c035fc53ae548a1103f31e72566cc82a358c448c55ab233fb9e72e49736e735d8cb8f23ff7bed73d3dd0da83d5f91f9c8c4d4acba508
-158 1 372 \\xc4448f841a59cc12d502bc056f71ef1985ef352d9f725ba321960f88b0352405acd4adbf56403ade769a175f4d1b058a88eebd6ee223b56d42b500e4f29d8707
-159 1 315 \\xc109dae837c5b05c3feb28a6eddb6c598b4cd7bca1450d7990a5eaeb86f717ff20da51f2da834386d827c6a9c1c090e3777ab494e8b5bbbe0b4d43b57946cc00
-160 1 93 \\x07650bb4ebb682b3365f2692ba808469e6782cb898533bf1b014ef398efecae800b654639c728332b7c5f186f27ab49448721ecdeca5228588e08143d051dd0d
-161 1 313 \\xea890bd690316cb330baa628d8d81bde445fb729af3bedac9321a7d84af9220c0835933a446fa56f4741e3b2f585ae508e8a5eef8aee1dd3097dfbe6fea4aa07
-162 1 78 \\x504f783c00958491e4eabc782b86e3dad315bdfa9a83dee6f018d4e459b3b082466eb5e0b0a26ab432d6c9041af7439363a0ce21eb5620eee07853521d21a202
-163 1 113 \\x65934bfac324db912a418555cb79746f9c696d241e1bf68622cca4a54ccaeb0483fce1d912c28242831547e841369fae276a2ca19ba5e1f2b8c151873143cc0a
-164 1 385 \\xa31ea49e85c73f067fff6f24fca2021fbe66ea4bbc2d9e7bd0ea18a28118b38be902c0209038b74ecae6f1d3161760359761d1d01ddcd79ab30033a7aa278404
-165 1 151 \\xef69e8935fa05ea07a3f0cf669f4e01fc2dd18c24816a77ceb38278af26c781af0cadfa7b124f3cd2b9aa4d77c790d9ceb03acbb3c5979f6872ed60c7889a200
-166 1 204 \\xeff5949d513ee9768948bc08d48a40e4ede1902dca59d9c1ab5a4934cffbeb535776d87b3d5074475bf5a9d2cdf828e52efab3e9c02cf320383e2705e57e7b0d
-167 1 353 \\x74094b85333836b05976403e5de8270b48de484fe65bc4ce6a3d38e98b56e1fd957f267032ffbd5eb197cab48a14099241292f741d1353531e1634bf2731ff01
-168 1 61 \\xcf4f5244c0773b4a278e68a605cfcffca3c65d6c85bd684609a1a65567cea1aacbab433435b509d99375485aa3a4e9c204c6ca1a4bea86c73b88895043e05b03
-169 1 3 \\xd6b6b18feb16c766ea4ea3b6cf9e88ee91a15cba530e7c0eef0f512116345cf07aed84c7a7e247e34111868950e4252f895187b5e8f9c96706fb6b625d7b1705
-170 1 291 \\x7eaf5d0a72bd48942a8a0386728ba1c1b68cebcec3fa6ac74ab28741dc3b1ec69fbf510fa0cfa08fd557ddcbfda96b357e3ea0560520c9fdf42e33b1267db504
-171 1 63 \\x5302913696991128e3c406729150e0aec00f4a5194d142b12be07a3086f6cae19a3f55d9c7766dcb7455042f822d39227a7a9fcb27a8caaa33f079bbd818b003
-172 1 270 \\x2ffb73dfede01d9dcad5fa92950ec121a126b3bc9c230f16044b1e40c4d79a86d497e6fa0bae9d8b0c151b064525f9c4417b9a8fd9927fd9ef80d822e4e35f05
-173 1 412 \\xb9f22f5d9eaf6998619825ff1cf55395c10bf41775eff0b4fa15fcd9867c0c1482928f24abc1d993e306118476fb99c0100ff0cd9ea77a9eb07fe6d2d3012f04
-174 1 324 \\x7346752d2f150e326c379a23e189beaf3b6182f6b089e805c0ab3e42c97f789885872cf280f762ba945548cbe07f8edb5d4272869dfddf58f8d7269766243b0c
-175 1 369 \\x9d8b3052e57c2973c6eb0b305d17220b19d2acd0a2096f2aafd74efc62fc27c0cbd280fa27e867fb016c985259c283b973b3de8bd51b85a09115d3ea22237a04
-176 1 260 \\xeea5bc92874a056768a872791acdeea8885bd30158670eaa6640bb453e4ecdebf9ba57c0e22e8d12a005ff4d21a8df2498b49fdf0e3c5ab31867631e7e168d0d
-177 1 377 \\x1de8786ad16c4fc7713fc5196a684fc253f25711933e1529baa5f367c30deeafb4b64df4d319856f89f5c9073e762862fb75580d34e84f23b9ce6124a41c7f07
-178 1 311 \\x354ba1c0dcaa237bf250170fcd986b9aa22f8c5ac9fff6d6db3a03972fa389f359242af81eb25d571cd21a3cd282cb7854e14bfe3706c68b99de9107a8e7330d
-179 1 306 \\x08bd7dbd019c1d78441707022a3a43b0fd4e480df06660b80d16f5ce8a2af4b7ddb3488dfe0b6eb601b4d2ae848123d524a265a27971d0e382a62adb1b817902
-180 1 396 \\x47f046fb0bd748c54d9d5dd39175c7d43e3af06ef8f7033db29a261630620e28f8da3094c3062a62f795d4b70219e7a5b2780573db33938d9188a052d2642c0a
-181 1 279 \\x8e8a1ee1484a0b9f44ae40c6024c8bf8b5b50579fbe21f90ed4d4bd5929b95dd1c3527f468da24f328d23f45506340b98b8fb35a22356dc1de9d25d6f3c4ab05
-182 1 41 \\xc65a9c738e407780496f99bf5952fad6df266dd0940769ac2280094158872b716d9a452b6b13003c0117888b6b64cb1f49a55308f98c70f78de0252b2567010d
-183 1 255 \\x6e16124b028b5014816c1cc939543e212f09065741f36c5b83b362df265442ebfea1358ecddfe686db6c145ad928ed92e4756a2cd1ab3b5776e9a98e954e730f
-184 1 86 \\x23a2bbf34cd508d442c78aec116d93d101efa6dcaa7e1fb1129122ccd59853757d1bb9139c862982af67dd8427e44d94ef97b0f04f89382a2777bfc32efb6a0d
-185 1 400 \\xfbe415a6ff34b4012a802e38276a68c8fe158755c3f4175692174e3f0efa88938817801ee0f274f0d0d2368fe808336670571bec91d11d79bcfdf4f603317205
-186 1 214 \\x8ca66caa1c556fcf6ff2d5641d679bf375723faffa87555bca333615efd5564ce1376394a6679b43ea335151ce540e7605e8260cfbff00386fc93226b32c6a0a
-187 1 375 \\x2b007c4b776d950303ed5cb439e2d29efd20ab911492ed9660a4d8d4fd873a4035a09368af86834c863fa8d62529993ccf920bd686c6bed0eedf5251dad1170d
-188 1 26 \\x1192d045a74bfc63c769c39542720e6656445a57143b36bd7f57c2912fd86e89719bf73f8fe3f2b56f2f02f46831bf3484707729f7c9c9c261ce869a49432e04
-189 1 318 \\xd497c2b9c2876ef3a91338a7b050ead167be21adab4c0636e465cdc63ddb9b1110b365365d7e2de99ddfab56d4f339902db6c8d1bb70b908b0abed0f58f73009
-190 1 35 \\x10022807b6f1b511fd76083031b696e09912766e6b386cf329e5dcbfba9698893ac5f60f83484e7c8dccd334f0df56c83798671a497bb2ef5a5428dd96e5f605
-191 1 12 \\x443228d4a1c14c9941df9c359343130d2460295130fe2ca84c23be34253f8180ca54384ef697ad47eaba71ee97a3791e4aeed31eb418f5df920a54d96cf4e40f
-192 1 327 \\x3ce62939da6288b1311122ca2f989e41a29523df46e680a1dfa49e413bbc8ae39a836a595ff79425db02bea0c23abfdc9ee7a399e58d0f8989f93866c3d3e402
-193 1 206 \\x01cb173564e9460630cad4204a7649dad40193d98d83aa5db32f000db3009a32f77f32911771ec0a7b624b9039ac166db1a87c56c980c0aa184fa933fc003f0f
-194 1 136 \\x86366958bd10b2841ee24ed10df4a3447510c21c7fe4ae51a517558825d75f7ea4f85d7684f06017749ff08f7711d583972285644075fc31aa676d772d38f603
-195 1 48 \\x36e69e3d48589e205752049122c2870d70007a7ead6fff607696d7b21f7aee1385124a8b502a2c70b3073ae123a2f5e4f51dbacd781989c73c87ddb94750d107
-196 1 286 \\xd5749a3c5b3cb2b9b9a5cb0eb0e94015c67f45fdd52691c40e2a7266bec9ecb8aabb4c5bac895e21f286590711e87fc183e935489a77b8e9bdc9976224d4c00b
-197 1 37 \\x0ff863f2a70cec80e6569b73084a97b34508cc25afe60a264b176c4317c8396b8313909b085db03b565a1551f54f1071e5389cd39aaa8a1c8936958cea65a307
-198 1 350 \\x0f87f05ecb8ecd5e34464d0711acf23004d7200598c8ddb316c796d939b8ea0f3621bd8df67a31a015b5fc5aa5ff9eb9de9c877cfc6235118b1ee366082ddd0c
-199 1 90 \\x5aa3fb981cc0477bbf93afa7e23854a92fb4402ccde5cc041cb22754cef818b1bc903b5bfef4d98bc0c8718514f5c652917124348b739573510151053416e501
-200 1 103 \\xa24f65b5336fe977bc3c1e9baa8247ce63e89e2a798b6a1a7c0b0dafed3a4d4ebe82a50f914b563a4ce76d9c7139a4a3a34266a8ff511778e49607632d1eb008
-201 1 152 \\xa869844f218d7e4dc027c05deb071db5aa16462cf9ece7c9a4a9bcf7d32b30b4dcff2a64142cec6460961fe862876ef263e7a2ad59d8d45da97db64d8efb1c01
-202 1 95 \\x0a960d1244a5b8fb6e7db08d0a34d7a19cfbf2b6b135ac6262378e33206e802dd4cf3e37ddd6cc31d1b16ff51d5edc0b09a76f18d9393e15b64c4773f0afc50c
-203 1 282 \\x8ae867d302aec28c21411b818c57074b40cdfedb8a6556dffc20859298b811156dffdf03802190d287c82b0f943552446cc2981dfb9ea3b04e7f692243ee9105
-204 1 249 \\x20fa2c32f98e7e92107bc85cc0698d68edc4ffcc6670b9d51929d7cef8a55591b94b00379d7a486c2912399bd44a25e35b55d5acddabd91312c28f73a01f5b08
-205 1 319 \\x6b30e5adfc83fc65d4b988c1b5bafac98a78cdf8731c864920b8dff2d9bbccf670b1ef3df1fefa3be9a22ab5c058207ea27657568c3874cdcbb208d470c0de00
-206 1 398 \\xbcbb8337b930587efe2a24db1858ecf00edba5dd89aa7bc75cc23b5fc717b0680b2781c684128cfff619be7b026397c0b30db87333fa021003f9ff162fe6ba00
-207 1 98 \\x8cd8908a624e554dc42ea5f0ce499d10af641b83f1e6b00dc6762ffc1d39d967ee906a9b060333d46e6f0a1e49fc01077d81eff16a85a827d7f213d6a372c40a
-208 1 280 \\x66f19f06d47a3f4a52a16432fb9a1b7ebaed5d8ec59f8dff123447b9518bf4c010e4977e0bdea26225a69de8ec1100924bb2bf7e9fec708c18f21804f59d3204
-209 1 71 \\x77723552669cef8df4085da0922f2ce2d9748d915929907b3db09f88c6263658eddaa6a8744afdfa54d91d231f8afb6134becfd8fd9976832c1792a48859e30d
-210 1 56 \\xffa369594479078bd93292e8f5e2e6af968c7f96410f61391f7de0aa9a66e7992720dbf3ae0bce7586fadb80536abced1f5957ea63c0d61a13a37660ae8a8103
-211 1 173 \\x8a8c2dc13f8c680f30ce2d2e4de4600aef1ff015be36b823bc7c6e6c4af7a9da22b5ce02aa5aebee17622c9bf806847b247ccdadda6f0c9429a87c05ea008f02
-212 1 290 \\xc4fd12f912940b4a86d304edb962e5a974ca0293a4e6f52da9039edd867a7c0863857136b5f89c48c38f08da290d0943b15212949776a5cc18c754a182d00a09
-213 1 15 \\x96b1cc421f973edb79fce0337151c0e95ecf04ec47351a72f3553167de845491629a1f8f92a01f8e34dc8903796b88d1482057d4e6f1f4879120ac84658c950c
-214 1 386 \\x27710a4c92d4665e8506606c622b3ae90232261eede5ce927d4f75724867326ec93be6b03040a664837910919feb7cefc5eaaa66e51380b7c223da17c9a2ac00
-215 1 259 \\xe41578f333b2a58c305198221ad0782777b9521076e1dc00f7fafe71a5cd738c3553e2bbd1b6457884aeba543ec0f471e832c675fa758195cfc6bde35d37950f
-216 1 163 \\xc6dd73c32a70d232fa61f6cd6b13d08b5cf5211fd3a1308fe5ca111ce843da9b803f89454adcdbc0b82e948659540b60791f2b7cb8bdd0e434c4e19413b23c0b
-217 1 409 \\x4f59e87406a5e86d4a8598adb7023def0418f4773f14634fca09e4c8f837bbd2271e94e14fdc92adfdbdb34f334db4d2951618d60cfc250ae55e633b53ae5903
-218 1 114 \\x607d39db12b28d54606bd792992cdff5e1f3a75192a29145865542921bbba5f156d35d402dbdb3debe716d8280c10e6d548041441036658405a91fbb600fe305
-219 1 219 \\x1c4af85f13e0b4e705a1ead3530b4ceb8515c9b39df17e15c87fb99a289faa9a5e7a980e92480c2669b93ac660881c2fb956ca613d6871d6234491096a6e8501
-220 1 267 \\xb0acf534651199bff4393942480ff8b2acba4637a2da876be87e9d025b20b02948dc123701f453974db160454dc1c2f31895bb0779cb92b0d5d1e59556517108
-221 1 246 \\xfe36c536e00325358e310e5c55e680fcab8d818c428ff4831e24507b03a747fe9e4c6b167c437bf61a577d5479b395c6b03457829a2af07f908d0900dfa7f90d
-222 1 218 \\x191cde049e1c1e08b2c29ceef1638d4823341e63afcf8b98be5e9746b6b9411bc87e15f3a3d3576fa0f9fc3d475d19ec34e007867f861a8815ce712398c23a01
-223 1 31 \\x0684897238298dd0efe1754ff6281716fd073690310d900887c2b9668ba2db3e7635dd46db802d514aa27717bb877b811cdd66a66049401b2cc4e80bfb698b0e
-224 1 75 \\x83851cdbaec68b71f8ef848655ae3d41fd8a66e6ca41add6fc71b99788df393191e1116ac0338d783a981978527d095620a21ae12e2e8b9ae80ba6560f6c8b0d
-225 1 359 \\x97225e40474b73a616813fc3a14c2fef198ba3f84fd908f0df80722572814af1241f4be5cb72a730d4eba42a30824c8447547bbf4b06a92f0ae27321bbb3a30c
-226 1 421 \\x6ec1038610c149455d67b08cc4f462db0afc0648c30d50d8272e668372bfca08e58a9b2a9bfee939ee1fa9cbf62232352991ef0fb8b0eac8b4b1e57118ce6802
-227 1 59 \\xcf902136780354a56cc006879135948331e33a18926f6aaf4e319652b3791a6140491cb4c5af1c493e343e4f046d3da9fe257c55ba063b76b4ed9d3159f2c709
-228 1 149 \\x8beaffedada7e45847ec13732f0947ed39c305b1e9f06437d5e41e9e111d5c66cc7790fbf920f2d38fabcafe60cff10b9504d104ec51faa9c282e1bb230d170d
-229 1 28 \\xe19525a95be0b311f2d4d3f55eb2447a9792e1848d1b774b7deb61b2df49642523248d215d10c0ca7e7c3a751f440139d4191b1f92c4147ed8b1e015d7179100
-230 1 202 \\x8eedee617cb62e601a958918375e36ac98d76f19a0b3369512964f520695bc9a27d2189a54751cd2094c18cee064bd7ee4ed168bc54b0e4b3f99371712d37c0e
-231 1 383 \\x615ddb8b49508de43b42194516dfd6028c799d33b6904e16c8356811a906d4e98aca150822f9ddce8b7c2c01135d70e8fea6729c6c9bbf8e732f9c62a9ff0105
-232 1 32 \\x6c73e9688248ecda3a6bc5863389be689339e65df52d80f359a8b726e689288f75bc1b871acbd8235daffe93558e3398f90c883250a773396a61097d2ef73e05
-233 1 330 \\x2549258093dac74d6c29ee3ed9868a50022469566963bc9ef7b17db8036fc77c07ca675bba42bc7839a211f5571e878c9858b2bfc3db19e8e6069802c6524c0f
-234 1 79 \\x1ae51f484f9ee0346799b5be6385bc6ae765cb6df534ca4e0df581ff55e561e8757c0e6265f9664a6c99bb8629acd751d1a6bea9260c4c6b202898c9a1365a08
-235 1 205 \\xdd5471665cb123d253512e233ec0285df6d8d054245db2262256833343088b940c278d59086f4ecca35c11cf21d3cbea0492c8fd5c7a74be7eff962c0b17100d
-236 1 341 \\x621a5de545efc51905d25aa9653ff0643c759924f6dd572b7ca6eb4f0b6c7747eed0445dd34e4ed884dbfc809bdabb6ff35706c224b538e5cb31ace7c4ea5b04
-237 1 320 \\x86ddff69562ced3bbc0c4c01a8a26ec6361662d1fcba4b05b6e750b3a3276b4d88e632241d30e6dc115969723e06671086ac0cff9673757e21b198b425b2630c
-238 1 237 \\xc53843b9dcb4a52bdcff456f79ae086cb5d0c8e252a8a82cd7db980a51af9116c71078f76d5a8386c6129e0010660a0c9da42e79cee576050e45f0db24bead06
-239 1 99 \\x1a86417b373f38a4772403e53338a064c527aa2494dbde84ce58cfcd3768970d2d115c8d397f39d89cda6b75bab1f54dda221158c544629558b5e18ed04f1f0b
-240 1 357 \\x060975aa054956fffdbee8857365bef37027c3f31383c60f0de86cc5a68614ec2cbdf8c5d7c5a5cf9ce272588b04db3883d1cd33a752afe468bfce551e9abf0b
-241 1 420 \\xfa348de0437f5a061d285182268a13419e998e3b9318f971f8b5305a047edfd6aafdd3075a62dd84eab199e393dc050164f68f5b048ad04fc941bd4d0f308b0f
-242 1 309 \\x049aa11bfbb8c2e676382c14c4c275b421d031c80d9c6a6f481f3ae2ab1f2b9f63785756fe51d694d57a813b8ddd0f8cb122874779789e033e4d6c8d61f0520b
-243 1 8 \\x34e6c3b4f96d3fc2d4151891edc8f9b3716a095a899a12ca044aa461f9dd79765317b15207442509c900f358921dd76e3ba84966874672f6c91ea841f827030b
-244 1 177 \\x435fb1985a8ad21c20a11df25e778507d71e03f23ec6e6e2dd4b5a763a9c9c18f44e48d8ff4d216c450d4c1f9ba0a2ea05f0c53493f453a96b4282422f34d505
-245 1 301 \\x37ad99f3c54140e3b2c1d822d7243129478f712cd62ca74b26a43eb76fd87b1b7d6691c7361259d5cbe81398cbcf2c7f9c55c1d713bf96a42da901d054d5db0d
-246 1 74 \\x8a2e12f43159071ea951f015786ca0d2a9e5576758442e3a11d374d403cff81ed8533f341ff5fb7d2c0e0df8f93b840b97d914373e9ad50790877ac7d9b9c008
-247 1 379 \\x0b29d74c0510d328146e8b5a64805a4afb10bd197b745636898d2c49dd4bac36465222aa92136007b21ad4cd276dc00b3fae02717d951f7bc67d196f2b3c1901
-248 1 30 \\x8bf9c6d1f60b6d43cd251416df0da7aefe4e3195bdbaaf0590fffc3bde570374d169016cc9272a45ee8475eee47670ba32bd28e31415bc200c3f58a407d3300a
-249 1 29 \\xda7e747c48334022f316ce36176cf92ff68ee60e14bfe0a6bc43455f1be856eab7f08b63553390adc88301641b82f39b0a0668436578cb9fa54147500e305401
-250 1 156 \\x79d78b5d63b02be1f1ef9b935313c5befcc5b4c37f693d6c370a5dd5c5518917044ee1c0f360ad7eb6b1efec1de6c9b4cc6451b7f7a84fc8c44ff9ab6a94c008
-251 1 227 \\xc70aa9451a0ea44a4291a43dc543d67b8fd993d30f73ca1ccc5eda6cd444bf3c1358fae586dd1b97f2a5df85fdce84490f3127274dce8bd630ab074c27573800
-252 1 415 \\x738f0d313f74bd20e2ea6af0c1fba5842286fe20f31583c01d680b4d71cff68e055d89e82888880c626d765683975c4fe5be8731cc3e42ee701df5c7221cc701
-253 1 150 \\xa8ca6e31bd36e6b7282bf44eaa9b967912503866fb2df331e0d33d2815fbdf7f124d368ddea1d1ed96feb030ae2b8ddc24707576e313a594cf36ff55061cd507
-254 1 316 \\x73215004788a5366da42d8dec4874790d72619b8f016881c84e920b08c089172d9f361c4a815a5e96a028f4322c09071bef09e09d6b213220238794d2c993a07
-255 1 200 \\x3a04987129d7516986823b04758fe7b81fb0d38ad49f03569af2abc7d3142505baf9ca0653728c893272fd05deda793b1185a07c579817b3d8d1d84b0468f10f
-256 1 371 \\xf9b93e84b02e65c0f832c6f3e2184bf46ebeffeea5ada72a7b430dc40761d3c3b5d688e39dc87fa31685b2a315378656702f048be0ed28b44dfab266c030900d
-257 1 172 \\xbaa3751c12662124c4793390afc97d7be8e1352b89a45af4bf364c8d2bfdd3e3e74e62563b9b7f03295b50a880f805e727d1751ba75b6ef16504bae071493504
-258 1 274 \\xcd9eac598ceb9cdc5e78408ef6b75c23c85a11c444efca30026ae7568715650750094f616f59f9833f54acb37a0d678beb584e9411c42cb70c9dbce27646b106
-259 1 165 \\xa141198d3550f8ade882da5662cb57e10fab99d248d2797abae38b1f902bc937a2e74ebc6b18e043a1a94af4eebe0392d04350501c981891f6c77554a4378309
-260 1 225 \\x4379dc18e8616ba3979b4ec16dc193a29ece3a3273272567f7c8a72bd61c79c4632aadcb96a2ac22648d369dab45606597f44c03d76d73042de034cb89654b0d
-261 1 413 \\xdfad9550783f86768b321eb704003936391f5c0a3852a24ee4e352b77bfbf281a85a4584fe4ed8ca499127baa849f6f6fa80bab23090884850e51ae33bfd6003
-262 1 13 \\x578d279dd0e1c17f28d4763f507a87f7b0e4fafbb71b59ccac7f9018e866ee5ec75d81b602a8f0008f8367cce4a7b1b738bf9376c11dcf667bcd5f679743b908
-263 1 251 \\xc4ec47345d3ab8ddbaf19ec83586a5e894cc71d86ba5ac4024c3d8ca2fb183128b4b6655e40d54a9bf84cdf17ce203b43c7ad49be856bc6025d1ca0c5d21eb07
-264 1 233 \\x0ade7fed2615a90762bf74aff07e37efa456da3a0c004d0b0e621971a0cae5094c68254e8e7121b52cc6f2d0d86151a641f36647abc09a95028a3141c0460f0b
-265 1 263 \\xb2d1d42d2fa777503e128c97279afd288ddce7c98bcf3b72998f44bb3c0a041c39c4e90c151f6f073d90b7e9d4f57d4c45746052f00e4d12ee4cfcb1d2542e0f
-266 1 132 \\x0345e5a6bda437f4de78b849a56ef530672134f42fa6cf9d675f7113a4b2b967309f51fb03f8ad5387134a7bb5588ee032e535dc767b17a86a33a0d1f12a5d0a
-267 1 411 \\x373e4b12038800058d6c4700b19bfdcbcaf289e4845e6aa168156e91a52555a4e4ca023dc23257f1160ed988a938f57b026fd8f1d05be7658c2296b83a581506
-268 1 33 \\xefef82dfcd5db9aa08a4c4675ffeff3b15eb596405f9d5c959e2e24e30316d23dbc4b3bfd7fd70cd6d4c591361ba3e867186b308a94b365492bc176d5b113808
-269 1 38 \\xe72f2f8189d1ca25cf30a3e945e6e93c7714489249c756c334c85804a9164d2e4bca2a8321ec761e431dc4fb48a4bef68ecf833e4112021940558c92f28a8b09
-270 1 9 \\x217062cb448c5d190f08e74bac9e8dd3554be12a6547c52966c6cd5bdfce47604fb8466533aff8d25f75cd7805ba142d5a9bfd87027ed561d684bc7efc943805
-271 1 334 \\xbc50238c6ceeabcd2d42c7199bf5c48d9eceb6e4d94c0e97cc81a4c8952f2cede76844eee7f6f4fcbb6be6e63e88154735ac102bc5e7c82e7f3fb1058117850f
-272 1 397 \\x52c69139765f39c7d7433e3a2ce1d8498c8e0c30a55a7045ec50c16131e67b2171924352f859dec76acfe51da03f5cc481cef3bcc0b97de1f170db53a6f02b0b
-273 1 337 \\x17fad236e34421912b28651558781fedae6aa84a444f982d3dad4317143dd405212e46027fdb32fa76efeef87884be3dfd930b54c111669a92685919f53d6808
-274 1 157 \\xa7334238f7464880b968aa39ecfe6510138a51bf04729cee6ba176ace86beb00f1230c5df7b868555696ff29ad97d6e830592c1a9b9ddc7920c5526992b33305
-275 1 112 \\x9c0eefdb305bca447790ed1bcfe39f70d689128d38b49d36f8a9e7705e98345a24ff4745b236447d20a66397b12677ab7e08bdb629e557362e140c0534dc3c00
-276 1 228 \\xdf59867ae90bd5a5110c321c00cef03c135e2e97c74351b9203fb8d9ad79f3680b46dea2a429ef3db4bcbed1cb54b3996a94a7ef0413bb635c08c821df7dcb02
-277 1 376 \\xf31811b3a95bcc49f92f6678d8bc6d4447c7d304b3f93210681f91240dc9af16a6c278ec78236e12a79bc903b3f437680ceae77c750390595503bd9002c6d30c
-278 1 332 \\x9ccf957482194232bf9bc36d988ac1ee70dbd9e9c559ab4b783b2a10366addebf61993174a0e92c3bcc32ec1af3a5ffc2f3f95e5beac68930ae26673132c2b08
-279 1 17 \\x7ebee3da4f6f69fd1e1fcca2922a078c89cc07d45e2268c118a11210c52e3fd02ebe5f6ad7f0968d8b776fd49d200a9aaf23db154eccbe74f4bb7ee35e5e5e0c
-280 1 203 \\xa8e321f00459d0b033aa11b68cc87f8dbd6f6bcec46e8e7cd0b88e4fc960721bbcc99adeccccc63bdfc4486066dc6672a567cedec62a01bd1870ee20a5059e01
-281 1 89 \\x517045dcaaf0e0b1d93dfb9d407d8224af2bbf33868d6a801a2abd4f4c65cf0646406383322f32e11f60c1b236d291f747621ad27ee24916eae85bbc54025609
-282 1 269 \\xd1a4ae86205be103d4edd4dbcadb445e7fa1d2f1e8e518ac337e9e857ed5367ea54f85054b160b77c54340434abb575fd0c76e5c925e0950f53600e730e6b508
-283 1 361 \\x3600a10e49a51796548b7b159d95fc9b3ccc1aeaf9401f223ff3792cd0c0813e278c6daa14cbec0c0374a6554c08b44b5a88040c05c066d2744b44dc8180fa00
-284 1 87 \\xbaf8396084435e259ecd58f77dc48fcc20303b25afcee1519bd2706ec10a27dfa18679312ff430f387c0a1adc5efa4f9365a58b1af9ca506df8d25d12b574c0d
-285 1 387 \\x3bd319adf8f1cf77a785d073783a6a2356d8bafac8c329901e53f99485e92eb4de2622a31904dc1a6a63b13f96476ee2392fd547fa5c050e01db60bc71725303
-286 1 25 \\xd3e9fd99c738a5ad949cb54e3200e4b4da5a8aea4886f4301fc9d095e2a402cff18a012ec5ad6c94dcf3aaa76a69d9c2c27dfb64858a9745ec8e7747521edf02
-287 1 4 \\x733be87dd89b9e4191cca7d211e1bdb0bac335b3012d85eafcfb259da7714934f82a446963d0e47e4fd9b618c3d4779c1fce156ccef20f9691b41f0c88662800
-288 1 84 \\x2ef3a87dd7aaf70646f7d3d29a76638c93ee44ee9e8efbf3cf3e5056b36bc105a2fafbfc73f4f5edafc3b6e651dc090e1c45b48a111e40fc5a044cc3e36fbd08
-289 1 404 \\xa7d25985b3ce59d2f5ca4333b724eab4d837f9ed11d2ff4a46aee9e835e1f5f7d157e403440f88b4d441ac5538b097bdfe7488367cfae6ee12e9a86e3681250f
-290 1 57 \\x7dd8036ce8f4ab6ce68cfa1e7243aab5e56c5c3484de11b52d0aa31afb6a4adcb53d79fab324f70afe176a82f70471ba6e4c45ca04ae65d0ddb4781f26a6da05
-291 1 322 \\xc5925a902f7c64f81107b3bd3951f9148defad75eed5b4caa12f578798a7c15f6e33299f1d387652c8e3e26478169fbc8fe2ffc3f6ea0ee82b9a7349aa048f05
-292 1 374 \\x84e2014c687ab11daa84a8282572ad504bef78d52565cfabdc3e4eb3d950e51dac22d42871c725d5a813509a10732b25b869729e88adc59f8ef11c2d17df2c02
-293 1 18 \\xf83a6654e844e441ec4ca8166d5ece4377f8cae37b002b8f82cd7e9f0b30966df628229a784e1e976f4de5d4b353ccf19ae8f6967bd99ddbf9992140d1b9190f
-294 1 105 \\xb34d961be21668d823b572b211d38ab4d363e11f14d13e5e2d254115d10d3844d13e4bb161d38fc11a5d6d1dddfc0d91d747fc76cc1bcf60e838d4d6a63c2c05
-295 1 317 \\x51bb00e7b1e5ede29e89c7fcd498f7958002e254d88628f84b689a94dc9fe30629f7005499877a247db4ee384352cfdeadb959e20b73250cafed9c9c984a3402
-296 1 393 \\xb895658a2c52c72afbf616f433ea328ead8f09ed586754918640ece30877e995df17ff68515a7ca44efe5df641c8708e64964662c3609434dfbc8caac6674f0c
-297 1 253 \\x0892c3d3113070b1dd7240df49738bd8154b4d9e12f154e186e3430b93eb83b2641d1bbee87ec98f55015ee71c565fa562d008f925d4e9268f67406f94b18d08
-298 1 388 \\xe3b21162cef98f1d91ed415e91a920ee0a7e1347cfdeaa1df594b93ee857791e61a1dbe4cb0b385fecbe37996c1c175ea606fa2e207f7c57586473dff6cff605
-299 1 161 \\x05db586d73bbcd2518451ea82c0a045e973d7f626d0b2c6540f80fda8006a54a56baa6d5ff136527cdde0b25dcec5a07d259e64a43348b2ec7f0fec6c3e96409
-300 1 162 \\x67de9255d89da7de9d06efdc56a9aed861e2cd339790160857855980e03ce022d5fa2fe639fc22cb9eca02af4dfa3441f55bc80490296299464b9c3ac66f7109
-301 1 67 \\x9ee8df7ca82564c02eca64da70e57b3f40de42c4a79b16419f36de88521faf7f9fabf0b8932985f736c47d0d8d0795cb628b32079bbdb982be5a47e440553205
-302 1 215 \\x2b1ffadf486413207348b6ad363e6536a2c00abf45b512f8bf4bd318b8c7d715fc0687dec93aeca99a4d3a19ea43f01e70904c1b8caf4d4080794fedd975fe00
-303 1 115 \\x5c0b1c38c1c41f7b51f7ea607310897425b0a5e704413695fe3ee9b06f170918f8034e05609c1ace25244f718731b3329baa99e698e6213c4811fa7a6c6acc09
-304 1 148 \\x04f2b60ed5c21c07eb8c3a7886618d1e508886507ba022bac991cb792a8f02c228a9e4f42dd5a97d9994b815a7adf7243765bd81784f12860c820d9dd0baf50b
-305 1 366 \\xae4120ff8470b19714a00d7b5184ffc95fa346443fffefbdf4043316100f27757f5fc3bb61b7e068041eb14a5a30df5ee1cdac8333dcef5dd8ef423dafe86e03
-306 1 303 \\xfcc7027ed22318dc80a47d14a433153692d1ffbf567cea6e96f737dfda0095c15a9b928d59f4532a02c58e335c9d0a1859e6e5a2095e0ce7a37c2e7a2aafe808
-307 1 285 \\xd90cb290dea23952abf26e1ef2b0394f20a9f12568ffeb077bac6bcbb08bcc363e75ded464cd6a5ff14b63d8496f795d3adf959af2fe26f69e3299cd6c992e08
-308 1 298 \\xbd945c90490816d5255d243c9f9b16108be5bc7550a17e378ef814a1018d27c14917d4135c5a2c0c2c5fd224e4c24610659bc80b6d61f4c79548ddd772932308
-309 1 42 \\xa6eee96f03ddc8f8db1b370126b68056a08b6b42c43b62cdd16207128dca1d59e9f34b5335216de4565e7c85656e0f73740c24ff1db0a2f6be62ce22832f3f01
-310 1 352 \\x980012ebfafee2225fcbb9a748a3bbfd013328225deb29ab69abca83195f6e291d7848b53e7c3a064b789c915eedb9dfd223f7c713b977bed84c70cc1fac160d
-311 1 336 \\x7b1e3c7221880e9f31c65117c2014279d7c46aa2a80d055eb5fde8f96bd9914e263044755c17b23a1b5a8b3d5c5438f42b7d6b62ca0ac3b47c65bd32dfdcb802
-312 1 145 \\x18585ce6251f2bbc1e71a64573d61cc9ef0b0af3e3c7d211a61170d4b979bae701e1f5713c578f402cc1a66a84ae3b30bc0713e4148e6144c4e7827ba8472b04
-313 1 73 \\x96cbf5ed12d010dcb06ed9700961cb871311557c3e54b8722fa06bca56452edbc03129d3b604254f52b5c07eb8d89fb8dd2dd608250d91fc1616e16048b7fc08
-314 1 64 \\xab233c31e8bb5fa38a5bca092e5c2fdbe02566fe9d777bb39d1cddff33fd6402fc8887ff071f5c8f53fc182d3085df6a0e8b6410d511566238fa0dc239a32a03
-315 1 192 \\xbf2e8b5e626ba9fd364bee10b375b8ba1d1d7f9061ffd1c17f6fc3187abb8c20446bb9fa3c677dc0ad23f80457c6849b92b9e358456707343628838367a1ad00
-316 1 416 \\x8de4a8595eb78b71d293db6d435e9be747d0fe11927d898c39a9320e3f2f48fcabc59b3fac250d43768dd9d20641ad66c2f31f177a82109229e582432f7f480c
-317 1 40 \\x1890f8461a2af155fd757506096405f5bc00c222b548db2ebbdf31823fede6e493a3e8813dd93cd758cff384aa18a0736e6c20a6b2154c5e2c53038bc6aece09
-318 1 134 \\x6ab574e2a865b9da5eb7767427e3f5baa8dbc0945216a16d21e071e1c9da9828637ec5778d9bc33bfed333e7a7142b9706146433f38c2c5ced9534197c83800a
-319 1 189 \\x19cfbab4f6fbb279d89ebb71a5a15bcdc2b616d99dd2018d66cc05461f5b2539f0034433eca6f5eb9a9f2a1dac5fab054e642e902c3d085e821bffc27947ba02
-320 1 419 \\xb7391a4f4f7bef81058e2551e90574c8bb92ccf203527e88951c4d0985c8207982206b5a6a302f40645d5c6df46c9157cc0f3841415c7ae557a4ec9704ad9d01
-321 1 117 \\x9b9f08f8f0c3dea7c746166eaa542e29edf612e5ee2809152ba59413c297119e2e8b6e5245101bf29d69235462c991271433c166457a581baa2c2d2cbca39e0f
-322 1 191 \\x5f8756e68133d457a99aba6c4b72e80d525834a375d22e082347e2cf2289370e4dd3a677a9ad713cb1d6ca0f2c9540ab7e35dc4b622276bcdade17380f4dd404
-323 1 11 \\xef2466d552c1c2d5ce5d6a9de4cacfe32c3489f5fdb7d0d619e4d9643bd54cea3261767c5ea9022b80a5b6514eea775109b5da962f2dfba35617cc1adb063c0b
-324 1 181 \\x208d9fd9a031c8a1c8e65128f4be394b5ab8b4e4ab18e2b0a7e15d9716b35805a74579cd70ba7b30df2d0baf16bfb149834658e3f23b7831696b8552b2785000
-325 1 238 \\x1c9441a57a0a1aee1fa387a097300a8a8908aa5822f463b3f8ea033dff4b7fefe4ae92cf39bbe44bddd96fb57d01330144db4ba373e9162c3da5ab31b8562c04
-326 1 230 \\xeed11a34ac313a47e94aaf2e7d5933f2a1c511d797a948f9c5b292f28a3db57a58b2c5a476f9aa7ea10c2e9a6a6fa4c9c69fc5c6ce1fb8597309557e573f2901
-327 1 220 \\x85ad685335f2ac95b702aaa3f23817fe602a248dc31a675a996a8002eff4b788a83a4ea87d20692368e380a492242b91de2db5f6ff015181835629bdbea08e0e
-328 1 351 \\x4db51d786c390ae2fa4b771f84d0cbeca7b21f394c1db53cabe65792ebe657b6f007b2ed9a67934a0b54d80404446a8e77b80e71d0c62854009c6123843cfa0e
-329 1 211 \\xe0f164a8ccac13fc116c6661a72a5ac50d80495cc9fafde3728c0d1a55e9ee93e671d9d2003a9b036ddac28f6d8ee90058ea2de957953818828bc82dcfbfb400
-330 1 410 \\x9fae7383508efc7357892597023ae5c6d49d413db28c5cfdf9f22f3f43eb50b74e2b5854f7c86f1873f8febcf7b7296952612d21e5182931dd4ab770556b6608
-331 1 236 \\x0b04bba0ff62b1aa216fbbf798f23c744f86165c75b751b87ed48f688a1cf8259b434fcba40d4869bc090cff65a13173a0018557aa2fb595cf9ad6c483274204
-332 1 364 \\x755c93b4dea032be838e3c7d88fa2c2a6fb0b367e08b899122c7050f9980c2911b195aefe586a1adef8500769b540e711a3f93ef03df54311e2578f9f39d170d
-333 1 331 \\xe3ee8154494ffad6162105c39eb2ccc271609d29b860a25eed11fcca09f14643daf62226711daf446248e918e9243aa6be4a454ab652403d899ebb5b7de4fd04
-334 1 54 \\xf56e002298502cad46322119dabd8b6ae6ba01dda6450aa106512f76095d027471d2cde26abff21d5591c6da79b66aefd0115c37738b9d8a3725024ea8518c0c
-335 1 223 \\x65bee5aeb583f2227bc6bd73ddf5435d38ffc3f441e8ba342ae79248803f5759b2e81fb0cecf4b3b1bb641bd6e9ce223cb3a89d318a9bf6ec51b65d4ceed3707
-336 1 296 \\x5abc5e0c7fcc6a03cb46d013204236842f8351d306f78f4ed59ad817aeadc06c2af05f70fbc357583bfdb0b68eb474e09fadbdd3651c18a1b061d4748297eb0b
-337 1 45 \\x4c0a8bbc25ad91c57cce265a8180998d60bb5d93e09813d03ada93e39c5b85690f758a87bde747483c211a6897e0bf9818c35c1bef21e6387036f01f6e32900a
-338 1 123 \\xd22eaa15669508b3eb37a0bf1597c1a7fed39bb3be8c7c48e6ca5788361cdbf5084609f6dd64bc67344508d880da3622362e6e4de11e3c81e87dbe64f8b7fe0f
-339 1 27 \\x5cfef5fb0cbcfa1412500bcc0d58e872058734ea0a7005dad3e4812399066e54b71c949e02279bfd907f4f496672b19d7ea6c278a81c927bb3c74467e6cad80a
-340 1 107 \\xfe7b81d8148a2e7b4cd6fd2d3d5493a0edf4573caf86ee077579b94c878cd70a951b223825b892ffc5c93778e232f64965236c7d6db1567dc87bbc71e3856000
-341 1 323 \\x12fa01c1c6d1a6a1ad13f9aa830f33499b24c997b32d3264c5028d223a4056b18c477133fb49c00a3d931c55f7ae4d00095d8a8f6609a703b7a79c78cddb080f
-342 1 402 \\xa7d18aa9554820608cf3f73b5760bafda34b52c6ec84980f626415a6e1bd7ee34501d378a9377743b1a71878ef69cdcd39077154f4183e26c4adcb4d01867d08
-343 1 239 \\x55a9e6b0c13c0eb8b5bd2e73a940c986525710ce69055e9dd435e134e3dd9fa1f7277a1497339be45889023b1bbea1aaa48aa8fa2e78c4578106809d9400d607
-344 1 96 \\x1fb7ba1573b41668660ed96262c6470af65a275264073dac1c0626989f0b6313a48e66254f898d40679aa3c7c3ff05d1154dde0394387d2e54e64ada878f2906
-345 1 2 \\x23f22822e1ea07a4f603039b59622c092dbedabeabf1c658239943b501fda5b1fb43acdcb522a6663ab0787fa9a02332447b17a98e9e5c8f8ab6577868182307
-346 1 24 \\xb3a8bfcf3baa514892441af8609d77f105bdef55d2e2c5428bd7e17609c56c4ef1e336195a10ecae982c0cc0e0250b117e198d92663792473bb0c50ce66cc00d
-347 1 257 \\x4239818442ccb487e2a0ac66d8dfdeb999a9c6b8b26935ddeeb8f4f5bd2bd1b61758ae89e018187ab6139256798ecb3f879eeefde07d7f0de4c04deb75c7ba07
-348 1 321 \\xdabce0317a3ac679e6f44516c6123f2072b599af60d3398c2d2cd0cb4ed77c652b06edc24b1859ce72ecb7146b892742fc9fff592145717d3cbfeb107e52830a
-349 1 300 \\x533f3ff1da3d0d14482f3faa17bb583b168bc79f28056a350a614c1923a796d4a7e9a9e502a6823313a88009d1026ded1c043946ce4b490beb89c15c66a00702
-350 1 277 \\xa94307597dc9018f083fe97f1753c99565b833dcbd6d4c3d8e777421dca5c8bc77d6d100a62712a41851e173113cc7b716d39df5facd90fc4d5d0c7df90ec104
-351 1 10 \\x9d161a67015541222c4071f6188f4093053265524d271e1172013b0bbb8dab6cf5c92c134de036a74cf83a4cce6f75dd4400cc8fa182245913beeb5cc37fed0a
-352 1 302 \\x1881023e1537d90e157569efd0af558e761ce1648880f49a3d922def30a178ac534f6f247a215aa19a316fa0d8a222d024b019bec0d06d38506fcb88b0393c08
-353 1 104 \\xb834f7345ae4c392333fc47ca5f5d6ae31ce3e56d47f8b6083a15b0850ee449d839ea85b64ac9ed91a1d46041218797a7842816aa2067c9e618afae82323d206
-354 1 405 \\x3e390f072a093d41c18fe1c7efb622b7e84a28e6638affcac3e33ac76d6f31ca569a98b86c82609180ba821b579a51d411cbfd87561dba1d0744b8e06356bd09
-355 1 118 \\x634335dd5ceeca77cb729fea1676965bf2d52d5839529c44a434f50680ee66995b220f39165b8e545df2434d8089093940b6ed4c5ab48011623af8f6d970230a
-356 1 196 \\x9e4a54692e6ede51917afb517bcf60fe74f78fc060ceb6ead202437209cb1e892dd1af4fda466658b4acfabdc9a55c8cbacdef7b36a66735f0e0d4cf501ce50f
-357 1 62 \\x69b9dc231975414f17b6fd7cecf41b3e3acca0760b084be02c32facc92e72af17ddf3e2ca437ed6b79d5b4b77a4a5c09391cff0ffe36e80d797dafb126e2410c
-358 1 262 \\x59cc3d25dff113fa2e08a7f3e36361062e84769f530e02be69628f60d2d7e49046aeb987bfa95d76181fda82b7d36452aad05bc59e1251455d61661299201f04
-359 1 326 \\x107d78aaf90caf7fad54a74cc7c6a573d493f54b0f17a7db777d62793f9317001c7ce3ab1a6d6f91bda4c6226aa4be54d2599c4e7393c90c3d8c49fa9f961d0f
-360 1 53 \\x0f19f71c4251f947b03b728a473528f081e590691cf02502b89dc5d3fd65d489eab646568cc3c3bbfa1a0b009963f8f960825f10c6f498195ae7690e6054a90b
-361 1 65 \\xf76d8147877a09e6977de5d1652cb994b032677657318a632f38b2de88a2ba6046fe59cadb5c763030a270aaa8c9f9cead55a6d2204830fcc3608395f7e0cd0c
-362 1 122 \\x2f2b1d2066d817c4abaaf7576dd532012f3dfa20dcc2b040c8907cbd674eafe66fda07ec8771f6beb964c3f3adaa945187c36db6f95498d2d695fccc7e948304
-363 1 143 \\x3ac694097ca07976e0b6ccfe532cd71aa92557e5b594d520c14f840ad52cbd794f440bc24ef1ca71781e67a1d0967486a308af9abb1db202f37b6059e9dbec09
-364 1 195 \\xe6ebfe0057979a23bc329ccce6f07658945d59e96e18306648c6b9e3b8d1559aed437f06034438be3ac1cd93b016c017b196bd8dcce968d9ba3b5eefa5c8dd04
-365 1 252 \\x2df7ed340ff92c413ea21c9ec86457c8801ea4dfb21731c5bc5a5a648854b9bdee03744a108d39196b8b468124b3cb9acd1f02e0e7fd0c2edb666fc2baeadd0d
-366 1 423 \\xedfc5538c66a385bfcab5af03392448af9657c38821721880f637aeaa64d5eedf2f1e65d55417b5173d7b9828fe4b2b56c662d1fdbe9372dcedcec16b80d3006
-367 1 171 \\x924902b9500b56b6a0c527da60083e2ec74f59bde574c6f2c6ae58689798e5ac7ecd055c6b45961ec2581534bd0e6ec72174d55896bd06688621c78d56537d0e
-368 1 194 \\x3b964f7661369a4ea4c5569437921fccb9aaae4e6b6a7c829826bc6114e0077144104df850d0314724c83eccd8cf1b15ba608acb3dbcbc05e16a1732667b190f
-369 1 106 \\x35021c88336c56d0fc37cd715e924bffe32f0e0a5062a34813b7b619d2bad112cd42d61a722c1619c5e52b5f9e4472041c726bd623390f6338aeac7b2a145303
-370 1 240 \\xfef01ad42f73ac076bfa865e0fc7235a9ed196c7344d750c2c9ea2143e515cc104aa26edb3af92715ed4526a29229b92681f00356f6d7cd8597bd26137aa8306
-371 1 295 \\xfb6a1e489acb6691323f7a77b7f14127e3ef6c6dc834125b1c2058e69d7249f323f5a1ea0fa450c1572c95cafe89c154ade9ddb72dfaa01752bccdc54b97f009
-372 1 294 \\x9684ca7fd5f949ae7363b61280c1b0a6968fb7ea536f7165da5c995b029bcfce9b0f5af1ed248bd6f3e85907e2551ec3a00939876d662d3d2b494fd97f8f7400
-373 1 287 \\x722521423a8e59bf7d61322b589c4f5db9f74cf3c56f5119110e0f414a7fcaebd716747a8356103d43c25992d317a57d4fdde798cfcaae57e801b4cd93404c0a
-374 1 47 \\x8e6f71098368addb6a59e54f9b6ab2ec79970db85ff3705dfc4bfbc327ea446ebadebbdada54faf4e53c3a45c018fa87abc7319b2848eb8805f550d86c99e605
-375 1 175 \\x7c96b91f8c34cb3648316f76df3fd090a8c9813747e07667bfda2e5e9fa9364083994e7e157a6cbaa5ac6ad1023fb41f3da52532a583897b11aa0e8a4c85550d
-376 1 212 \\xfad1a550328005ac76cfa9a57edc01f3b1ec080958d52614de1b2fd15ae6f5b67c7dc419f72901446ab80488589560078dffe37bfd5d6c916d3a86f6399fff01
-377 1 289 \\xdd7f4ca7a5c9042b0c8f1f767d68b087fa60cee188e4d9ad08d138a5cd670676f9171a4e20429f75c410347904ba6404c879c6b0c3b05c7bf45b8f0a21c9a101
-378 1 292 \\xa6cd5d2d2d9718b9bbc9c1c8a45eb88140c1ec4a3fa6a746a60a204224360197a79442fec5a231c1e761c79256affc46a6a10e1b2a368b54749693df47542107
-379 1 312 \\xd383994bbdf96ed9f1774aaa4d64daa628428d795ba3fbd9d88633a1d9ef39cbd04e97a9d11a4dcda4041a08a82ded2c02244591e837d8d1bd8d67e82b13110b
-380 1 179 \\x84cfd3fe1b426d0d460fdfb0d8014bf2b0902e4c61d9cd9c138a70d0ff542f44019ee490a500ec490c6bbb82bf484bf75827e1a625fe9be7451634441295d502
-381 1 344 \\x8c217d2fc69ab8c19af09414fd1e2b26ffce4a71d0004801c8626bfd1c7f42ca8061b4ac367de48cf6d292105b88bcb31b4e0603290c363e04090568a3f81d06
-382 1 101 \\x3087ff643cbfc973a6891f5ee6bcb907b3d96de97347d2424f63d5358b9dada8b413293673be025e871a641623e9fafb995d66be4dff47c76d33a0f302ec2805
-383 1 308 \\xc966507e93321ccdf771239d9ed207d83ceab3b3e7e1ef9118502ba1e2f766b07ad7cbf86d519be434bbfa66cd576b8235d3f8ba7344af10f28bee5a69ef4706
-384 1 16 \\x981408e0c00b71b219d3c51017598afae6cde6d115ea437e7408af59abadfb5275274108ecab6a37e0849590539089cefa7d2ddf5f4e40013713a9352df9f20c
-385 1 414 \\xd1f23f520ddcc7879be216e6dcf5f3dd2b104ac1e765f99f9f2c0dd634a2f4c7b264ab288cbe3b0a663eb31105b54d6fc54e709f6f6c873aae3d3688512edd0c
-386 1 108 \\x6325bd3cf677c9f5fe367dc5ccf8894ae4f0f6369c7a1fa324056f494a82d449a34ff35f2fc5efc36e636fe4fc7f2f0d71035f8a7b03c6d4fe5c3e0f112dd809
-387 1 137 \\x53de69a80cf0c888a0c55f877f4d0438ac3a2b36628987bd87b51ed6b7a5b408572af9499fb4e68c9c2aad3d36d9d8028676fb17d2c7b07953eb0ac77c54fe0a
-388 1 392 \\x34bc5d1ff16da69dee1bccaf5641c2e650d83696b77331f8f6bb7bd91e1807e7f63d20e529122f17a12c5d0e47de99d6db26df1a46c6dc6360bef26ee9254008
-389 1 21 \\x128533919b72e89875982f86c61f259d7094962f08cd1d5b4b31c489ad6d3d5cf9730ef2628b9e286d3d3813a72b8c8335757a2760d7eb7d37ef710d6067100a
-390 1 82 \\xcb3c96972ed7a2fe88b332f7cd865167cd0a6069da127ef8cac35d82fec71a095ec0e8f06c35defe77cb6d6892f22bb0d8f6c2653f8ab982893eb3deeed27a0c
-391 1 167 \\xe244561322acd518cc992000a2c05f0c1d57d740e30b144062072b6a4be81f9a1d7ec1e3ba1969e32a6319bab5747908a433b739021f418682a95fd4448a900a
-392 1 373 \\x7ac41422b7a8c5b001c6939ac8a60dbe8ce32459a82b9689de3c8b90a5797f75029860ecdd0c313b54b8adf85bac73784469a7fe923ab2412657c5452e111402
-393 1 168 \\xa661acb6a797a6f20f119be78bbd30d3ef8ac64173b51adb693696d499c5d69990b8ab0b145e650cbfe56ea4836408929ef30fe17b1fd1f50681a770ca24070f
-394 1 210 \\x3567c2b2ff8f3efb224d4279802215d301e5e4a913ef7cbb4a1a6afdd43efef48bcb6f74e3797f34f6ca8407b4c197be017dd8903b49e1ed631199dd66615b08
-395 1 102 \\x2ba68a140a3b8b4998461183bd4b468d7b8c7c6896ba47bd4392134f5ad62a84404e49264f577fc439c7e465d0f1136d35e209a3940be8b4d755f09bfa8d7803
-396 1 97 \\xdce0a094ca22d48f14281445b3980c7335e793d6a89abecaf2ea89528db85bafc7e2283c764413acaae049ac88661de93fcb5c93d4b61a4cddc6cc2ff299170c
-397 1 245 \\x13cd6af0df761690faffc8a21c9094fb620bc5daffab47d8be75fa020f11eb9dfb17721d240e5edf08cfe91d9e327ceb548bc2da76a7c5ce10a6e2aa5ea63109
-398 1 91 \\xd5e8a31d63f0feca8945104212e5c67fa5db174c978b161bb35d134fcd1a402fbdc8608c31654a52e649410524e8527dd07e7b98942b25d7e7161931d8aeee09
-399 1 36 \\x51f5aa6ab78edd9c12b4c97a2f053a2eb8be2f69b1d4de74c49128d98b63ba51969d6d19ef0182fbf47af942538f4298638cc5dd91ecfb6b752c1db79bab650d
-400 1 394 \\x1d5dd9d83faa72c559f59028cf3af5def024ed4cc4c46f16725b58e431e0b62882200d1a96fe66a9281778e704941c8704c518fde63d6020fe3f671c2479990b
-401 1 391 \\xdd66655f02adc6bc627f7cb5cef6ee2fd0f08fc30909714c696bdc72c47553b5e1f2f07184c87bc62094f4ea134c0de2fcc84c334a0c3c12ad958ce2220ed604
-402 1 139 \\x884bc1d4264d572a2b368d3f0bedecc2c179cb31732c4cd57855d9e2c87e4393ea26c20949d96560992f402fac046b687a8f36cc4a900fef2107f80c1864a002
-403 1 180 \\x1d2f646c8222fbabb71f216758f9fa3f36aa8852bf99ed232aabd3d30012cbc162b9b7163c2cb3431437a8ebe40548c0bd89ad92d13ae75be330d81b4e2fb80a
-404 1 19 \\x7c0e6380ca6d1731f88de25e54f239de7724f37ff242e206c485642ac9b4f6c894ba5147b6f8784d4aa51b1f929cb0b91101ff4b43fb480175ed2c918b193502
-405 1 51 \\xae999003a917a0dd422affe49d069a9419f562b8ee27768949e0d9be264c6b0cab164a6ad0c2bb239d66814bf2daa31907eba5a3e767ea161c5dd4008ad99504
-406 1 160 \\x548258ec2baeb50e12fa6b26c810da1825e2147c15aecfecc5ea960344f919d4b646fcfd3013e78b157c4776c0013f985256d9018aba870be6c67b14b66c9609
-407 1 346 \\x27c3844b6c9f20b10aea5e868de11b2327b6befd4b47a738b88fedfc610564ff3d6c1f9cb0bf91c475aafd98a21d83ddb1b5c6edc3f2f24722fa0885626ef70f
-408 1 83 \\xd3bbbec9b86ffc3e25b210c47de30871d1182c7d97407c9e2fe3340336dc4312f9623ac5079ea162076067c3126c0b17385e0f02d259fb64eacd035c8de8f306
-409 1 221 \\xf2369ade84a0f0f25f3d24f651d23af25ca05a4ec608d99bd5092e2dadb7f22a195ae85cf5dc2cfdeeb26cc880079b9cc2e6740175a1f86c95a06dbc5a65690d
-410 1 6 \\x65d8668c9be7444cc4e4fd64613b130df37e878fd334762b396262ad224300ab004a011cc91210b757e55d55705d7a80020e6513e03a7a9c5e039a518578d80b
-411 1 193 \\x254b4238ff290712c89382c1379ff2f12243df5073950ac8eaf54c44a7ad05917dd769d5ce5d2f3bc9893657b22094721bb4174a254f9592581d6a707abc4c07
-412 1 198 \\x81070ab4eae5d7c103262c85323fd3ba1135d144d4b901cf278c83c6072685081869ae198ca906642f1d822d19b546cfb4ec27a93d1c5684d47ac96be88d9402
-413 1 125 \\x31b591b487f71f9b7cf8efc2becec1ddc1c05de2eb0f96061ebfc3823ce2bf1f85d584b7cb0d064a8d3e94a42147408670d224f7b4632a31cd5563fc56ca4600
-414 1 304 \\xe79f386725756f92821083d54be464fbd2cd3eba456b7b36fa0a0a28e63e4bac9fab0dd7f79d8a41af7c2459884a76f120d55c6c6c9ebfabd8f29c76d730eb0c
-415 1 128 \\xfc4531684d7050c38c4d074f06aa06cc94bf9b2b6026cf373fe7e07da911e562204d00312b647821a1d951f5b5b4fda36f19e92f56fb04382ad6a36108d6d702
-416 1 72 \\x438808da9af8a20fd365123712a39adeb46668cbfceeb422c6359cedae241fbc915710a8b8febfc454d97d8a49660486c2857092e6c3b443e403e819ee89e608
-417 1 224 \\xae56843ac3a631d122fcdeeb5d345cf9e9a75b5833dc8efb39702922721f01ef44e218eec7f0b26f7ddbced1ad17e12adf46d3cea39d5fe15c2fa1a5dc442808
-418 1 335 \\xac92970e073ddedce5a006c658cdcc69887a7cffe332ad29273c593a1b57d82a78a1929a3289dd11e5ece60868223dc9b2d1b471c1ec2f31539290cecbc9040d
-419 1 158 \\x771d2061561fe89beeebc4f9b0d9fae4538e83dd18a4dde0592e45699cd8bc78b6bd6fa6a50fb2d4ae633b536a9d99eb613a0bc46736b5db968e758647e27d05
-420 1 268 \\xa9d7266e4b1130e5dff3b0d5b6bc522a578d6aa0832ddb5750fc3647e7e4c610e6187505cf1580cbd6e064d9f91cd0d410caac0f1038dd0b5b01c5c228886a08
-421 1 368 \\xaf0ce8d5447930e8cfa8064bc2d9f6a6277eab0f5f7171509323fd827ebaf15e119215b0336be809829a9a34aebe8922b1e322de816e2012543e78f4875dee0a
-422 1 155 \\x2f2bc03e7437b75ba49f998e8377d19fb09b96b46ef5a3938d5bf7d878ad66cf1c1ac18e58fe64c47feb2506a08669aaf607de47797798739ef4bd47408c7408
-423 1 256 \\xd25f64e96cab7fac9156ee931370f9877b39add82a43a9fc47de16abb0a573fd9b76f365c3e4fbde95f98cc340ecac48e943b1eeb93c7471de2b3f0fb4571e0f
-424 1 116 \\x0c90ebc4cdb40485e09158610b651c25af9ea4958b8d57a8d4d6c9dfcbc71db7d83d1ab42200a21c24325a116549b12b0facfe819ec5d04da05bc38b9a7abe0b
-\.
-
-
---
--- 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_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;
-\\xc224b524084bc7ec63376db8b4899f2ec3b0482bb6f8890560b236cdc4e167bc 1638356907000000 1645614507000000 1648033707000000 \\x70e9063063f4dc2e3532a458ab590fa6c322362e45c3d19736b28f770d79af0c \\xa96df026c9d79a0556d6ed540e8cd4465ff397bef464c3d644f401b3f3c27f4ae4c04a21463d16f75d1275404447c02778884e4370086400c3eca5e4f6d19107
-\.
-
-
---
--- Data for Name: auditor_exchanges; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_exchanges (master_pub, exchange_url) FROM stdin;
-\\xc224b524084bc7ec63376db8b4899f2ec3b0482bb6f8890560b236cdc4e167bc 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: auditors; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditors (auditor_uuid, auditor_pub, auditor_name, auditor_url, is_active, last_change) FROM stdin;
-1 \\xfa3cbcde17b7eb2464ce6748d3c2a01d554a293a7df671aa6aeec13661e895f0 TESTKUDOS Auditor http://localhost:8083/ t 1638356914000000
-\.
-
-
---
--- 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$260000$letq19bXadlaHoWSFEuxmy$aXnWIfiCVeHVJf09R8T+FShPtObYSF1Y9gverSkElgQ= \N f Bank f t 2021-12-01 12:08:28.4642+01
-3 pbkdf2_sha256$260000$mIyB9Rg63QGEGzwm63hrJU$0gMEV5FWd+ho5LVhjBjAT1iFddhQGZIIi4QDGnG9eWk= \N f blog f t 2021-12-01 12:08:28.715205+01
-4 pbkdf2_sha256$260000$dhgKBUvLIMyqs1ZM6i3Y4U$jxhUoN/crNVaMcOu3N0e0ZvQkiZHeTs39GK7nHfjGgM= \N f Tor f t 2021-12-01 12:08:28.833414+01
-5 pbkdf2_sha256$260000$esmZPL2jgshidCKiU7hIjo$lDWvIym4IVTUWIW5fnZ3J64elTDo3ZUJo2+0SDdoh7A= \N f GNUnet f t 2021-12-01 12:08:28.954942+01
-6 pbkdf2_sha256$260000$GU0WblgCUlMmoTTvDQfN3i$Vzb8kXPO+Sb/nLB4n69P7XziHw8iXojyi8UyQnNwW5U= \N f Taler f t 2021-12-01 12:08:29.069344+01
-7 pbkdf2_sha256$260000$d9QovDC5Pv1rDLCng6JlUw$P75kw0ptunQ2uvcDtuUthrDudyq5YNx2w6fe2lMetYI= \N f FSF f t 2021-12-01 12:08:29.186117+01
-8 pbkdf2_sha256$260000$TRA2E375xeJJFNp8OX4Q1s$k693S9aD4/KBrX1zjPANqLph0deaZDXjsr78HNsfRsQ= \N f Tutorial f t 2021-12-01 12:08:29.300074+01
-9 pbkdf2_sha256$260000$4sr1dZ69tJTvCLxVwEhVl4$UEThXQvbobxzE+kwJzKtmzwkIGw+Vk21cQ9iZx9HNcE= \N f Survey f t 2021-12-01 12:08:29.415324+01
-10 pbkdf2_sha256$260000$g49r7ACpirnYpmc0YsfCTn$3iVVFhIZRQwVk/ORGgotGIu2eeJ4g3cSDpzne2+YgrE= \N f 42 f t 2021-12-01 12:08:29.844984+01
-11 pbkdf2_sha256$260000$TDta3lixvnQMagolTOJz7i$8sfprB5ujs0jTCJtjkpGQjJnr13WQ/PtqG9ykmw/k14= \N f 43 f t 2021-12-01 12:08:30.269862+01
-2 pbkdf2_sha256$260000$mHdqYFFgOubxxnaoXP6xzM$TgxmAhbGh85neHKSqyF5kt302ItEtFkwTFxmWvykzH0= \N f Exchange f t 2021-12-01 12:08:28.587107+01
-12 pbkdf2_sha256$260000$qjC5bAJyGnsYNCM9EUGSIi$4bfRzD+DAyCM1bf6fzKxwAy6ASMsu2/SuJi08ybkqWM= \N f testuser-7tgipbkp f t 2021-12-01 12:08:37.738719+01
-13 pbkdf2_sha256$260000$feG2TTUagptXYjbgMcQ0Uo$j6Cv1dzDkMa0RBGvcgRsbthZ8EtLnlyg5NB5dycoALs= \N f testuser-db6y8s2c f t 2021-12-01 12:08:49.946975+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, denominations_serial, master_sig) FROM stdin;
-\.
-
-
---
--- Data for Name: denominations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.denominations (denominations_serial, denom_pub_hash, denom_type, age_restrictions, denom_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;
-1 \\x029c7a06bcdd58acc2786a8db57b359eabefe6efa729e49fad583b160e6b40b4b1eebbc71eb11424fdb1fe5caf5dbbb29c29bd9c2a8fb5d7bb61cc3ef161f37b 1 0 \\x000000010000000000800003c44f782d431d1f8f2ff3ac13cb6ccdfef2354fe756682ccfdd2715d5f5e15d0b83c8517f16988d0598ee8f144b3692cee91bed2ef7bc690ef13f63e97bd721fe222a053d2434dfc459d971f289b9bfce456d3d60704219bc1b3d1a14c0e91ac0bf72872d2ae1c4ec6e0131c3958d847c5e5324c496ff8747d444bc57741d7f39010001 \\xed8307c77d6b9c176abeac7aeb5cd5641a1526d65aff59ce01a8cfd54dbbef7c1ae81fbea3c7f570476885b6e484705fb5982453a8f59e9284f62538d7717a0e 1658909907000000 1659514707000000 1722586707000000 1817194707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-2 \\x0200ccb719045dac20c040dbabf40b6be55382692118b14b0493d0ebb849f3040b73a60b722fe2cc7f8da423d3bcc6d9b69722e0eea6679d3007973822a4705e 1 0 \\x000000010000000000800003de45665b1ac54873f32afd45a8c352088df710f8d1b3a571a50f8f00e70d7535d57a9e35592fdf9d0cabfb097449ab33f3882d743894c1c70c691c7a5f546929fadd79d96c66b48171942a84d9e066e37c91390fb894c76bb84f4eb43d538d583f15e0ebb1c21dc1bc3e6ffee08e539a1cfbfa790b6c340089f8a4c6d9576ffd010001 \\x89bdc199a1c309fdf7465b732a19c6db5950e474ca603575fddb3e23a4551369e763b227350aa4fce44e911f24a5e1fa437fb3284f1df052e9e2f88502e45c03 1643797407000000 1644402207000000 1707474207000000 1802082207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-3 \\x07784278bd17ede8568100143d622362c356038aee9cbbe0f79996d70407b43874ab788a56da04c8e37fa630182d1b61f552995de97c23a8c0d0ace0cc8e8b8e 1 0 \\x000000010000000000800003b3cb194ba9669ef19b30bc437548c8cbf5e6a7f2bf66e6230d051c9e6e3f87030e6a336a0252ee068879e685df58b06486edca567ca3cb8fe9339cf78eb2c330ad03f1a6285e47c42ca5c267fec1c0c1410bef95d545166c7389239beabf92894736b1a766ac97fafaf3f470db9cb3b3dea1bba6ad77dd76e94470698c01ad8f010001 \\x31c372a71bbf2b56dc3a6d8aa621e6f81a58b9c498d5b26fc80717aa1ff75fde57dc004dcdea301663404bca256e68786307e15e013a77b79e9d538ca0a4a70a 1657096407000000 1657701207000000 1720773207000000 1815381207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-4 \\x0a5c5a18d4499dc81d5aaee852bee118b405c5670a6c3b5f8497e59bc5c945bb1b05bdf16116ebf79272f0d29a492ac32c25840ebaecd1d3033c12573d62be7e 1 0 \\x000000010000000000800003c5527c21afebeabae11e5629ed15bb62f9e0ff457891fcbb195bcd4edf206c4b8e1811425fd6ad50a7a6f55875337399991e2de18568f1f7c7354498643384dac6d619d17a8c94e9c7d1f85c9e739268ad3e7ea7bee96813df8e102847e04fec7d986c2d2f7631dad0cb708bfbee1bcd82e9661f83cf08273286ec370423c6c3010001 \\x028f25536e5241dd4f42ff588a833a2a1ae8e6ba5969eef0a03bf88c1f702d66c2957a50997db576bab3b95a5937ec6392e5a4f61ada07522cda35970c367804 1648633407000000 1649238207000000 1712310207000000 1806918207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-5 \\x0e10adba8cc4a6b6489b6908363f2696a4fc8d48167f25a0c409046bd93aa47cf7d0681165dbcabee127245eca177a413e51a9c8ea4075779396eb21fecb3596 1 0 \\x000000010000000000800003afcb3b7f4fb0daa23dbea00e265a006c73dd91262b722dfd48831ad28bd9f193158ec17518d3cfcf4c659339cfef8874294819a34cc925c309dc124c601c63453872cec70369cef65fbad24f3d96aa0166c244a4fe8b0e18e0bff5af74139adc0946845dfa6d4db4e57748fa3176f00248cc9865938b346b813a53fca372173b010001 \\x833616613581c8ecaa24706c12b35a00d4fefb560501446a778e533bdbcdfebbd70eb0cc1932a185949b763202fe1a108c0e78d4322c4328b5d6d83d496a3508 1667977407000000 1668582207000000 1731654207000000 1826262207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-6 \\x10f0ade480faa4c987ebf6517206027e3a311b807b4d3d3070b67d70817417c2d1178ea5c82a07883a0e9722033d8acf780662e753b53a8167fbb5ec8edbdb39 1 0 \\x000000010000000000800003bf4c01faa86becf60793237fe303127eb392485c08ec70ff2197c4544b6386123755e8dc1078cc611d0a5c9112aa22ea770e1c36000425f677bf9b69161a6f919c24a0f345019ba8d906f7d73e9f9924798eb375973ddc29ebb9b29abeba7064bd4c0deb1705d1b8240cbba1fd4194cfa94c23e9424990b7339a79ca1ae940b7010001 \\x2d5755cb60a33c37c59200f4aafe84e5785207b8c2eb7349511d6e2eb2a87f6b053c96902ef094503d9a662892742c2b4a6077b80a73d101d72135473e327d0c 1638961407000000 1639566207000000 1702638207000000 1797246207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-7 \\x1328c3ef43f0eee88ec7aa178f50c963801d312680652916464fdea809ba7b82408cd67f1f3d43b3256393eea2080721dbfa2483baf3ac9f127de0262b3acdfd 1 0 \\x000000010000000000800003ad8a782dd66375138ed179da36b76159787f8bd1055cd61866fc4dba010da4f28d86392329dd3f9b0c4af9c79243e098dc93d17222b0c905720df3232ec0b0506248438720514b85929657ad4612038d0d0fec0d9faeec01d3668762e5e1a0d83aeacad835b10d5958e6807da57027560109a5ba92989bf2bf384da2e500e80b010001 \\x187700207dee671ddbe110274305cfb77d59903ab7c69bb94a6151bec4cf330ed1c01f84b746407bda35092f143c636631641ab3aeac3abca7121d08c3e16d00 1666163907000000 1666768707000000 1729840707000000 1824448707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-8 \\x1570e7c259089423fa51781a88835b0310d72ac346de645de6cfb7ac49c4965cd6148f613a7dcd02ef8ecabf0aa25e8a0c170848b646abd5ac217368d1facf1f 1 0 \\x000000010000000000800003d3b92f35df645721f2b2b7c4dda5bcdf32074ed0027a06a71a04113caee9e41ce2f2cfb195b623263d43d6b7a06f7196441d59f9f36a11656a5cab5d16d228abb71a4e78300d3af81bb524607cd1606fad7eed9f892545c3ab4cdad521a7ea75c31f2a2925f4ea08f1837c18b1b588515fa6162e7103bb7ca5910a1d9338288b010001 \\xde4f2ec41b8926fb60fe5f1d23d8cc20d89a0638172ce7af1689e8c7ee36dd427b14bb03d6475989518971d94c15801a56e83ced74715cd92e517a0c36c7cf07 1651655907000000 1652260707000000 1715332707000000 1809940707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-9 \\x1a84d06a28b27976400964450bf661c29459b09df3e2bdeca1740c8e3f328e66cf51e1452767bf6b1666bd93390a5ba00c35fcd2730ab1d7aa0dec20dcf21c80 1 0 \\x000000010000000000800003ba4b64370aa2becfc56021444f919705dbfbafba2c2ec7f756249072582d149f3903890ecc3ab4cc7ef12d926faf30faefa544b0f71a48e9c1f25fa252079f96fd15620814957d761ec1c23b8cb39610e4a8b66aee6e8dbc5aca5a4cbe9c9d6ced10e551aa02b690590daf73d60943a7f367af7729255c72e4ba6ccb0ef88107010001 \\xb31e6e2f58dff0a1f4c8be94b7f2c40146ed79d5bdb1e6900e6eea83547017f17786584760c0a0bc330eccc5d78d8f120203f2339533520eae7598179d523c07 1649842407000000 1650447207000000 1713519207000000 1808127207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-10 \\x1cb0cda243429d5aa15bd42db21f079fd6e34089131828a243d11c2128c446c22639d1a9a96da71751c073818ac279c267508a6e87f1406e8b20fa5c1aab1342 1 0 \\x000000010000000000800003b9e8b2f0fbef44aa39ba01b1f36ce55415bcca6481efa4606fa5cdb7682ba8179d3bc81505b217bc1e4829110f43e706021dd40ebbb5afa991c15c19f52cb6b6f5ab117392ebc3aeca99695b2135b083520347d6d6e5e3fbd68e2550473e3d323027214b43df7f680534c3a9e9a6621b97912df3ed1cfab5ace88d85e2b80ac9010001 \\xcf656fbbfd6a083483d8494b1498dbf92a28f5d8c44e9881cb435eb9b820e45f494dafc99b96f8a77ceb9fe9d7e2f56f0a8acf7f78a4b6b7adaf4c35483bda0a 1643797407000000 1644402207000000 1707474207000000 1802082207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-11 \\x1db85ea12280687e27fcafbb13152104bef3ac8c209094e1eb277f7529049f0c7eab7457cac0c0d2d059a6f5a2d3848d1528a5b5dda5b921f51c16bc7e3ad597 1 0 \\x000000010000000000800003b978a37809aae1c16c37fa5ac8bc242edb827dafbd403a530ea3dfc7c9b2312b91e70ffc083d81fc865e1fceeb4087dfd47e68ba5b1d928d2ff6de124afca461b573d665f320a4f22d641ea393ae194f93d175c2e5c3f683d00a53831e26dd4e306ec674e270bc4fc47ad4440dc4ec62ef87a60526a318c933666bff8120850b010001 \\x4294d49a38ed84b67cbc9030e1b6914e9870609aff5d3698753a297622a3100cfa01f024d7dfd27a5e483d4fac4b8791587324ac22b1105a7d91f5ceb634840a 1645610907000000 1646215707000000 1709287707000000 1803895707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-12 \\x1f9cc3ee27f1d77c2f1662f9ede073090282dd3f7774333a82388f92310bf8fad73e1c8eaa14c738795480a2e05acddd9ea1a930d63785e487681215344b6700 1 0 \\x000000010000000000800003baf685cc634c67cf3a93dd580476be6fb116cfd2c58018d9a2fb6ac6975af23230c55aff714d53cf91648b43dc3c6616d19ea0ea8b105541b44bcb218b760cf9210fd249dc42fc247e21f6c22b584e9034636e9866a4a2d81bf4c254d8ae23adcbae80e990cee609897dc0d1f9c1d5c0886c45dea0326cd01f037859d9fec889010001 \\x2ddb6ee066d758959837fc2e277ecfab7657666a92526eb0a373e46cabd2f9f4b553a992432aca705e0cf2cf7f7018d0618f6d416f72b50ccf59abd54039ba04 1655887407000000 1656492207000000 1719564207000000 1814172207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-13 \\x20dc47eb80b47c6b37d9df7078c90907cc3ae6089fe9cfe3dab8d5a969f137ed7f7d58e9c03aa293759aca7f5bfaa3229e4ae1871698deee5a0a67859a4fb721 1 0 \\x000000010000000000800003a80b9e1bb5699056cf9f6fc020011b18ee3ece1412a4675173dc453bdd45c1a73d20e5a27bb9c76be741a3027531277e24605c84797901d0399ffb11e1eb1119469935aac4529773d86f9aa6c5a5ad0b70d384caefe8703fd802a38e7feb1f9e3fc2842075eb685c8b5fad7a85eb56b9d01952bde4eb4b25151b103a46faeedd010001 \\xf943d12f1bd9b4d7efc12e2c454ad493097b4a75300e01e805186779790ec2fbcfc95e416cee2ecd69ec9f1c4566e7c75c7e9c94ac491497ecadaa0709693704 1650446907000000 1651051707000000 1714123707000000 1808731707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-14 \\x2274f7316b666ce563edd9b1d9c3da2aea20fae1a69a7335cfa693f4090eb3a2d19971e2581061e4d6d8ed5eec3e9df763e3a3085fb6bc251de8077fe754e68b 1 0 \\x000000010000000000800003ca2faa67e1a43bf12de98b33292a57c5ced81d75d771e776b61ccf6ffba56a0fb9ad9bf8b730d1415033dcd455db2d0ef1710cb81ab8149e21c52d56f414300190d5fc4a41f03fdefb247bd4c6e72cfc087276134365225f8a2e8f60f5241069da3f90c28078f71425bf7909e8eaa95861e97358b7690da320763a9dcadfd821010001 \\xcf1f7db8dd0d5fe53e6e05dfecce2c5437ed0c3add3c74b35c96e155fb0dc7fc739373edb9368efa6c056799a3554a9e53e88991c66eedbc6867a517d5e5ef09 1661327907000000 1661932707000000 1725004707000000 1819612707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-15 \\x22bce690adbbd4b4a5fe9919e4482a593317341dc37821340147d13b9f10c72a3b43e3edc042388cffc03dbfbc2a015f2880464d76a5777befa0546c7bc854b5 1 0 \\x000000010000000000800003e87f94ad9c14978ba72d4b72a01f0a16012883b3cdfc57a3f87d4b9e7a43aecc3c0ff360de96df0efab9625dba6b4e5d4410bdf182609a5c37c753f9c08a218c985f614ed5acf71d9c8b43bbcb6c31b859a2a04e62ac41b842af11dfad92aede45dc439d6d486d2d79e30f4f36100848770df13f2767b20f36f4db20205821f5010001 \\x6febbd0d4476509582c766a38dfc5c7baee9024d9bb8d07926cc1e9d00a1c3679107518a9aac41cf0dd0883d4d4e14709fc3a831daba7dd7cc5526034399ee05 1654073907000000 1654678707000000 1717750707000000 1812358707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-16 \\x267883b9f6d24047d10440c442290b71f20ade5d3abed0c9073e1fce98e1222457dace466887385ca5fdb3a1d37c176284ba0790486f1a91c72fe91fff1fd737 1 0 \\x000000010000000000800003a91218506ae211a73992b455f81bc5cdddf868598dae344a796219cafed5815285b70b82ce23af24ba3922a682573af7bb4ba178fc6d3e2a620ad0980e53e037eaacfdebe3e85a573b213bdf0c7df0dc444f3f06bcdf6c0a33c1459f36a15f60a9506763a743d6473b631d6fb3684ad346261aa5abbd1730e426d2a5b071eff3010001 \\xf651a0ee0a15e9eedfcd5743dbe1731ef7490b6cbd82d754a3d2e5ccc73d1e8806921cfae15a388ae87da10b9daa5be65272fc047e5316402fefa075c1646b0d 1641379407000000 1641984207000000 1705056207000000 1799664207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-17 \\x28d4a3a532603c9293de7b7c7831633032e592286540aa8db82d6fccbbaab92bb9c843ee56584f22f19477d61150aa30a4271386fdf9ae5fd4c428bf5e6949ac 1 0 \\x000000010000000000800003b5d182cb437989bbb137925064aa83be4ed89179bc0c08a17ee1198132bd8e91a51c28c07745364e23ee2a8b42521debd9b2397b09e3c7cee4411dea88ff73f1282e7d4b761512d20bb9e14cdae3ce0624f5268e9734ea344a332fba28ae65d22ace6eb5deed37dafe46d35953ddede92d18f1e38ad22c190decccf2bb972e7f010001 \\x08b503ddb2d544ce8da3b4eb0905d1e74c422966a12ebfcde6b525bd927811d33407b50d4ae316097ba397c037bc6a474ea36e0fccdddc9f533809a6ffe55a0a 1649237907000000 1649842707000000 1712914707000000 1807522707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-18 \\x2920007439af0e3f0df5988a72b44a8ba160a9618887981c9f54a681496c89f732910984ffcde03aa32f456c4e67c3c024e2b2b61706ce843247cf885364ab7a 1 0 \\x000000010000000000800003c047ac777cc432102cf3eb4c75e76a6d14ac255807c7bb913401e4edc951bba358fb0d6158ac5766d1bf550e0763c35e790a2cb5b119f53d94ece2076fab4cf6a5b25f55337bfc7960ac3d4fe716d9ad3b30a86432043b39f049e7211a77f48e0deacdccf2208b8c1377a053177753b97ede1cd02aac71573bb9674f29a6305b010001 \\xd134ea18878912318c93209a0ed195e5349c0f493ac9cda981377870c6eecf996871fd6229595d66f6aea40dd4660b325bab9a38d3f5d9d5b39a935b39aa510d 1648028907000000 1648633707000000 1711705707000000 1806313707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-19 \\x29506ef959d47bc600708cca837c1e8b255b7af881cf7ad3d98325939ba6bb29d2a31236dfa063f7e97ef387bc2c822216735418803d5b7d0ad85f08fe41da0c 1 0 \\x000000010000000000800003ca2c9934d35c87ee536951a9b7e67aa8e31bb953ef2ff1ebe33de8641a6e144b3bc1329c1cb8a0dfde85f575f07f0536c5b9f4c5e4c96590fc3ff76e3ab9b204784ce828f7a3d31e53eb7815b9c819d376aaba0917373fec056b21088ec1a84c1e7b821092ab4fe7a7a003d286effd9b82a9be7e6add989f76ccf81f1aa678fb010001 \\xe37651b918cceb7b58c84aaafeba867451f1ad89b22a098d530170bf0ab2fdb87e6e6a5a3da8f95d1e35a237f0e18b64101a29f62266579f67ea1d463c8aa70b 1639565907000000 1640170707000000 1703242707000000 1797850707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-20 \\x2d702502804870c04cfdb599b33ad9fbfa607f4ac5e74b13214415bba6a857e1bc6f9baa1c47dee1331f444675bef062bcd865d4395e67a6ab44df235edb5b76 1 0 \\x0000000100000000008000039f2796ec4a822e9be3689526972d2fd608a792aa533917555d2232bea056b5aa23119817915fb2031de5bf73794551bcec5c1a60f4874440e6e149fdf0d1eb0b3dfd1024598faae6eb2428c1f828ea460754613ede2ac60ec36ce5756477e565bfb1a116058987772d6999eaa1e3e2e4d4d45f111d6d335149c127733e463673010001 \\x35e3df148786cacdec7cceb400f4ac0c87dad065b52e239f6c93ba2356c01795d70c8173660336b2e109a6e6846cfd7110b8189438f13871ff37cbf8a4ef0f00 1667977407000000 1668582207000000 1731654207000000 1826262207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-21 \\x2eb4d2c63176b458850cc47286bddc63d2f6a899112df92beecb5cbb35ca7925d2d155777dd51c2a33d85d3b9c21e66b19e1fa9bd91dfb08c4975383983895ac 1 0 \\x000000010000000000800003e04c8b61f88d41107f81cac79e50b4682dbcbd84da8f09fc4ae718912e62ff9af104bbbb6bef281e81fb6a974faf8e4c82bd17ad20f9bca02545e0e45b10840447361bdb3a7743b7f8512e8902899dbe5d737f537325d51fdd96a858d785816b1bd86d0b0935460caf7a33800ae447877393d7edc60a8e922ab6533d0c10d177010001 \\xd67773d4ead4a937ae7880e2dd0bb27a98b435b41b0343e017f2d372bb0d454dafcb198631757753378ec218e91ab35dcfe069ae46b33604ee2c16f56000d506 1640774907000000 1641379707000000 1704451707000000 1799059707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-22 \\x31f8bd40f559ee4ff722a7d9f548135993223db9bf1594ececb9e2dc1174eb70472f6a5f1f6616f0ef265a3e44c2a9dc77ef22c8f8f6fa099b4332c46692c4ff 1 0 \\x000000010000000000800003e84fdb034945a3d421a7cce922dc22f563a2ec7539b9335b7428984a01bcf7d083b863f3b9da9440859873a42cb70d68babcf4d83a7844e23da73c854d7414970710efb58f7cdbd1bc08196df9a7972d1a9369f5b16345f63fb5018bf501fe205f241bccdbf28e8b7b62be875fd9f9b135b7b10e7461a4bdfdca3ae018c535db010001 \\x44747292f7a16c85d182290f267d28a48dba1623e99f4dc8c5cb627e878e67e6952b70799439cb368c45069462a296f2a0ff3b6746d2448bfb5704f96cd2e60f 1667977407000000 1668582207000000 1731654207000000 1826262207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-23 \\x34482eb9de9088a0ba2beec0911755cbf0ce43b7663fd15355197d49bfaac4126a7de1fb41cee390f44b464441518355a20624e39bc5a3a3fca18fcb73c7bf83 1 0 \\x000000010000000000800003b2f6fdddcf853b7ed6b92bce9817c637827757003822ce61db1210f2021fd5176a2fd88f9f22a06653fe7dc61e6dd3ea868be3e45144134a56d0a75229b0547859d98e5d52073db8fdbda3530a0bb000b52594824673494263d4b141209d455faabe02a41cfb88604d429ea884cba761eb904fb1e35d6be25ed4dcd03ece48ed010001 \\xb2585ad3b478e3796444f44c9335e88e8d3cf66618280c849ccf1841a11ff7f26d72c18c5610459d659fa3da4dde1ee77ee7184b15c55ca9cccbdc7b43df1801 1666163907000000 1666768707000000 1729840707000000 1824448707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-24 \\x3590346b4c2dcbdb43535e39f92b61a5535a59a786bc0c64d4c8cd874a77e200de5f9e1276a69e5d2a52a983c6263e4f88dc10a375aabd0c73901338e06b2aff 1 0 \\x000000010000000000800003b3cad9157576c3c6b835c4ce2421150c489ac1b63b220e7c675c2da7fe17bcb038df428fc9f0bc86e3ebf1443b14bfa50856b7f10ff1f485fe5342c4fc6fa51140861f3a4131156f0d799094ec3983dbd21ee1737687fbc960103e3709eb7e70f39054cd323c9de8a0150c835180c128fe51764b52b3043c2ad7bc4e0f2ef039010001 \\x34478ed0fef6e60fd64241243a048669be7dffb4d555b9afcf05ac4379b5b48c6e0255c37f03b32f94bee853062b47f8544768e07ae01c5fb2105adab2556a06 1643797407000000 1644402207000000 1707474207000000 1802082207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-25 \\x37a01e2b494e11396eb2b9c3d103e9f13bf9255c9d78f673ed430737770e2e857190abc9f8993a8d62553e2fe6b9c4f66d0eb59e9fa3fd93f1f21400b801a90f 1 0 \\x000000010000000000800003b47daa07a7c053dd6b5178a39b46544bf289c9e226f1f62e7718ab6647d98d3597165144ea9a8af64ac57d04cbc818ad67c497193bbc5b88e11d64dd5fbeab115a4de65a98372201f014a71be5397885a76ab3475b6f64e9e206365b5119fa5dddeb1d01e3fd40d5ba7afec206831ab6b26b97960874b093af26e9011505b717010001 \\x08ae6483857f11add615035e48b3e7d7d8cbc3af60bfbb9580152a1a8a98ade8e06e720973b6344f71282d6e3f6b84442efcb2925226c6834cd2f2ae36e75807 1648633407000000 1649238207000000 1712310207000000 1806918207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-26 \\x39a43c3ba43138b0eb46005721d6a301fe6abac764cbe2c518c470056e66c72f67ba6c10ba30c413a15c1573c4f8f596af68a05afd25390338bf771355f8f713 1 0 \\x000000010000000000800003ed178667d0b8e45c3aaad1b8b0b1b4d80b83ae78b18ca589fe0d57c57c7edd34feeb8668c8e315852a512714a17eedf247774c0098ebd458af6b62242c252dae109691f5d1f3d1147259743dab0597a081b283b0e724bf1368541eee760fbf9c8d81e07e560c5206380e9e7298779992f1cc1b945819aa44af43f66f03882dbb010001 \\xb630be56e269bd5cd1fa564fb27873d6f7d0bcb04b3610aed69862c5a52e6ae7f8e0ac7b491a2b579021e76577aa9f251c6222252755efa95250ea93c86acc0b 1655887407000000 1656492207000000 1719564207000000 1814172207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-27 \\x3974d3a3e1fa85f79469313da20cb4e9e6b7c3c77cb462c4b07b1f196df80133fe71403dbd222bee832a27283da418144a36fc9c286d3619c780bd06e597e44a 1 0 \\x000000010000000000800003e80b07843edfdce76e7b0a6c1995c1b99e2955151f41e9dae1a2fd61cfc624545d5864779b4862aaebad8c42435f229e5c46257ffcdda381b60c19c90396de00fdc76a109c7880ece73e1d9d7161dd47ed7b33e2be46a481b35a2996a8d13b8d5539a3f1aa3e13a3b37eeac97c01e56cc6c30785767aa411581ff2dda43b0581010001 \\xc2fd01512536eb325e7b42665f38c5623d3915a241a6a87bf96b7fb159e8a65afe0b21a3b3d48ce6a0ec695b85c278381dffab8cace0a3a286d114b8b472ed0d 1644401907000000 1645006707000000 1708078707000000 1802686707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-28 \\x3ce896519d7a46698b8095ccd51c43a29e0cafd17e56bc667ed9ebb74d0f8659fef5dc82b9ea0a78a57f0c5e1a7252172ac3f9ee3af734d781811d8f20a9bcce 1 0 \\x000000010000000000800003c730b00ca5e63f5d68dce9a4de37fd3b2d27f95c021f44330c6ce8af7783612274be46ea7efa6a59d22d1c8826dfedec357f32c73f360ea56fd3773dbc39262c0a57551929457426c66543f8b0d71994072e1f95d9ae82202dfb769ec90e52ba702c880f427b79a92ac773a60aa9a58999ff696f12aa3939a90bf429e4e0901b010001 \\x61d628af1c1f2252854ca7ca37cbd855700ee5f1cf2b10a577d684ac682b8205000ce68c4a11a4420dfeee9a44485135dbe0917d3475a2e6e57cbf11f22ef30f 1652864907000000 1653469707000000 1716541707000000 1811149707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-29 \\x3e1ca6a3b421ae8dd9873131180f57def90c7bbb2f629825de2618ca6508afab97d76eb04a989cf411e5ca1001025d5e526fa4ee5db4134c42d9f263bded398c 1 0 \\x000000010000000000800003c841869535d3b9cce6aa48ac165f9ba44e16d23ee57c6747e67d614a497b032d63922b6b510c7da8e398242a6ef2dc62c56b04481045f59f94940048785579e76e0cc461f459b3b6878080e3c9b610f7f555681224a8038cff01eb332430b2d95ad19ef01bae8eb55a97054bf990bd60161393b59eed7fa3bb191f3ab2ce9055010001 \\x5d4f8b8a147940b53beee4205926aa513f507fe0dcda2007978ef2a799e7c2daf07e2f5c95b6ce27e9d552b655dcc51a5098499e84fe5a590a82dba6648fe108 1651051407000000 1651656207000000 1714728207000000 1809336207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-30 \\x41e88c910bd773d1be374c55c953b2529b0eecd1cd43d5a9b7421e684f73acba60a6ce8b75622e40f397ad1d2e15fadda8a60b4a34a58a84abf1e86c8b50ec97 1 0 \\x000000010000000000800003e7f6cd72617f9593484bc87d53a9cbed306997325c1e4a54ee1a3af099f7334e943bcc5842e597fe9040ccf3437aab1bab3afbb79ca9c9dd066db7e4ad44ffa5b9e97c8d3ea789172e0585469e1424753695c97fee14a0d408141bce54f4bef961d6e2465f52336e55119c7582ae6da8050f34a9a2e4f4e74502985c9682a321010001 \\x29478434ec71c1e615bdc95e2321bc2383ad79cf1827e7aa96c158dc764ce6e4269e811f7bf066d89a1f7af182055bd6c32925e1b3efd874d7760ab59a3ced04 1651655907000000 1652260707000000 1715332707000000 1809940707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-31 \\x48000068bd33a418f8950f07d8a623688a6c942e87b5c772a259cf528be8072ba69d0203ba55ae199ad37aa4b0fd3f2a63a243e8b7168a5a934f477c843373ce 1 0 \\x000000010000000000800003c9da5496622af38251582cfbee963c5bdfbff8d5eb3ec75d804a48bf000f8cb9ca5470357d6e3f20dcfe429361e4b992e2dccdca6aea561289f2df317ab212db1fa1338d8823b551bdf21418bf9f7b3fc3a2de28448c87c423da86f132c95ac15bbf88dbe47f2cef504d97e94d2c1acc6e777345903dffb672fba65c8bd0261b010001 \\x09181ab859a3ac4352b35cdcc2d96502ea0dcb397830c24a1d3c00b228602142d81a0ae41a07f4b65be9cf7036fdd353950adc25b3e9cc35d6d61da8c4f8ef0b 1653469407000000 1654074207000000 1717146207000000 1811754207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-32 \\x4b1c57e6683eb20e322a75242fccc3055bd200e25c572f99ed24d9de0101389f06610dc2afdedf041213b7a98aef61a642d4bf817b8a0bb52b6d6b7057a6d8c4 1 0 \\x000000010000000000800003c6eebc95720ba253b9b8528837949d3ff7608da2746bb0901d50cc86acc5ede4d4c2a4589ccb72d65013e5053401f3f441ddf27b508f3a7ad1d3d40248ed193de8f0470d0c2240bae799010fe39f64c46656436ae3208f5ec78f1f63be4855cf427466e1cd98e485d611eb4a8ca7ce10867d8e24f421f3d028f8f51140482a0d010001 \\x1108521e129118e8b32fdb52d4a3a7ceeca4ec92afab1d00f822c030b38548a8c712544c03f21b56abb5796dce3250870a3a4a7c7bbc98d0ca822a90b95eea0e 1652864907000000 1653469707000000 1716541707000000 1811149707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-33 \\x4c30862c37d3cd15f08656d6cdbec3e22d968bfdd8edee1d907cdeeebecc19302db78690c092fbb998a254ffd558217ec90b572f0451dbaf9ed6fe4e0f5452d0 1 0 \\x000000010000000000800003d6166c8071459318b67c8d7c889629f7f920a1f4a7b9d10ba0830de9887f0c831ba4822766f79c9255b523c92b5e720103d76ab7d43ac13f9d5f035935f5f1d7955fb0bc490109e88cb614ce045a0d5d9be94459d3fd610f2950af05e982230eb036ccf3ea9e9cbb49c511a49570f9621a6aae470ec2360ae961fdba3f88b269010001 \\x18615e0e4dbae57131a947aedb2b0f678d808f4db92aff18447f9e91f2b2dc7b050c5c863dc529ef9a7b67a80d8775e44747a3e2fcbe4535e5e1803087464d0b 1649842407000000 1650447207000000 1713519207000000 1808127207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-34 \\x4e7c98904a47a8eed1b93905d110c38a55f6e96dd43936716a9d45fad810c5a6cfa77ceb9c5016ff2d4006f03f3b5721b7e82da3600c94b8a11f9ab57f767719 1 0 \\x000000010000000000800003bbf649a2e43236d1f489ec1de0a467c7b7aa37689023f29cf6c39feffced77c37849240e582ba3ef0743b83ae01ad3057e443264c497ee32d4672973db0419e0022a364680c4d978f6172c38f86c9f2e6749aec8cf3e1d564fc8086153e2472edc31ef164cb61dbbeee4299cbc207a17604c239ffb49ac427ed9f73db6abedab010001 \\xc9046d4adb53ef5ac8e90313f2718ce1edcca65e1417930da9be05c5a3450fadcc6154c57017cb6f471570b4dc3861bb058dccd8ffca04a64a508f7c7a3b5b0d 1669186407000000 1669791207000000 1732863207000000 1827471207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-35 \\x5198952f0eb58f7ee44cbf8d2661c7b360eec6360b35280b48a884f502afef90b952958502ac64d9d28c49bc93722712ebc6f3197b97e744be61dc4fee3dda92 1 0 \\x000000010000000000800003c6961da18ea0e839d675bed810241612884568b7e49f343110dab0d7b201908bfb7751a000907e6adbc519019abbed6487abc1b7ed96239d2a4929f40d5f467b396f09231458e18d9f3742d1608315928d3e548f34a2e8aa1374d62cbf1e6a850ae0d1db7184c0547170d5d5eed0394a89fa2d2da83f212ed5b215665bc932ff010001 \\x7605270fe13299a31f6aa881af8860ca558f2ee393d932050abb81e9784d03183c77a7d063cb8e6a25fafd0a1682904f09078dcbdfc2bf8203d3f0f8b03d5f0c 1655887407000000 1656492207000000 1719564207000000 1814172207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-36 \\x513c47a9f58587e0e7a070e9c2c5d39cfa2e0cb0e569341c237a7fb967be89aa80f476f275eb849d11dec5de43bff74e94e68f097c70fbbb4c7441128b6487f4 1 0 \\x000000010000000000800003bfae1ebc13339737fbc047f2b8a7bcfa1ce14ed587ec0b15d99cad5d730e1b7b8652d5d50a1f8d9098f1372de0dd58c20961ab50a186d1bc8ef133a1ea8ea15482fb6187b018abbf62147b1ed61bc9d2f2d3003125e59eba7d3a57d1866ccd9397a0caefa2299775dfd8cc31de600aa88fdb8b4b212f9bf0c5e34d325f187f77010001 \\x8a44c1a588d7d7647c1bfa07be77291be3be607256600b44d8710a4eeaeef1b7163705b4598c5550ea411bbf0a11b331e182b450937c9975ccdd96475ec25406 1640170407000000 1640775207000000 1703847207000000 1798455207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-37 \\x529c21b4509c93676bf3a16500fcb8503a70a9be1cce9ca0655f22ec21c9175be29c797da64d4aabf79ae7d5051672d69c3ad8352400c1b75f0e7adb3d05c7fc 1 0 \\x0000000100000000008000039c6cc81207e410a107986fc0c44a32f13b5c296d87f17f824d6cb62712e694cac93d86bf2f18e23ba60aaba6faff413a0b44f6b37be571e11d279966f654274ca98a1a3b329d2794961f9cad7781a0c752892e09dfd469c2eb85e5252793ff16093acee52ef9b9fcdfd4479d4f2fa4c00a3f6e15713242341aaf6ad4dcaac067010001 \\xa68e52b2e7b5e7a9c3d23816fdc4f00e3de7bf41e57ca51c3c621e2c835dbc3f11c7d6d6fa699e25e304bde2131c9c3a0a959cedaa19c2ed872b1eff43dd030b 1655282907000000 1655887707000000 1718959707000000 1813567707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-38 \\x58f8e5de818b09ebdb9af715addb97dad0d528438c0d19136cde2591096456775cc76fb6a1fd8ddd0ad38e24865faf3f3ddc6ff99106d8ff3c2482d217eabb05 1 0 \\x000000010000000000800003d9ba0c5b68cbe17554d8f5f55ed1696ba527a2eb3b82dd2db883050b0330b17b30bbca816618166a442fa483ba4c5707d071165ce28e508e0b7d322f1c3424773fcd13aec42181894dbf2dd56877f07374895866173da420085970f650830ceb1f142f50fee20fe5f56e25b8ee4027b21d7dac91434e3465fa7c5e968caedfb3010001 \\x8081506f731aba9c7deeaae5ca028a021314c1b4344f5fafd0eaec0bb429c2e24682233e4cd121e0fb6818e57a03b0f15540c435a6b41237b0795b1526c2dc0f 1649842407000000 1650447207000000 1713519207000000 1808127207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-39 \\x59f0988c218e10395ebba3b1757d47c22858a620aa237f1fb0b9b32bccd8630b9c5e18080a0fde7d823c1bd180c3c412e273d533af2ca7403420f314c0a907e2 1 0 \\x000000010000000000800003ad97a1098ca06e3800173d316ec01aac72bbb3fac1c6d81032fad7af9bfe459f88c83c182e9bded69a77fec74ee69c6ec59115e2785bc3d17811d5d89b8b0c0d16a9f4453c6c54df5c8430371eae997929899fa29aeb6016219832c0d2fe6ce587ab3b95cb015ee8ed7b95392210107e79c4c23c7177ad42f2f944b454cf2af1010001 \\x40a8e1e8567d823f9c5cf3176759456ab745e779c5baf2d23baf83c0b9882d951491156dc7a9a712ef4a64cbfdcd533c7e9c72f2c19fbe4dc5dc71d572bde809 1669186407000000 1669791207000000 1732863207000000 1827471207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-40 \\x5ad8bd75a53c0946f6d0fc693c501b747c44dd8f0352e9a3f7cc111660bb11d4a28323724dc644e0426eb41e148cc3e8ae32ccba00b261aee24ea7f417da858a 1 0 \\x000000010000000000800003abd024d9c74dd282ea50c116647e50e0759ccf0fecfbdf928f82d37c6a6f14f2ac40ab2ad3257395b59a346c5b4b3a380ad3b35a52c2edf0c871dbb5ca1e96505e05f211fb7a2f4b15e43f99a077405dd9aa938a5ba7946f3a0283e31c17647bd0b6108129e9d6f7fb86f051fc7ea66c9ee63c20ef824828d0243397b4f3400d010001 \\xb2845f7f4483575510663394a590ec27aac74b1c61814eb9d9873ccfe25a09ceb2772903da6cfa834fe9bec83f98f1c7f2a6f46362fb67f1fb40338c8b6a8407 1646215407000000 1646820207000000 1709892207000000 1804500207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-41 \\x5ec892975b6818431f1136a04fb398161ba3e7dc68a62bb609edb53dbcf7ed237c78a6a12aa06e2003331f0654acddb9faf9210a32d28a83ea3b0242665d8f6c 1 0 \\x000000010000000000800003bff538b6039f641ee26cd984237388a70468aae4aa89b373da7bd6bd78dca4ac73b0ff590b71cadfd7e9172d249f25e6ed3c42848d33209b25ad427edcc560558e25c9c22a90e86c55983f9c2749bb542f4b6c90c64aede40a64ba12dd1beb503902dabe2525f6a8c2330d549d90ae2cce4b2b4c1a6802e9a3982da83cf2d8e5010001 \\xfb08bae3b94d566b2cc48f529a7d334b9d1c4bf0caea3f08c754b68c13b7ea88155171fb7de4d0bc487aa8603066f340ae437e20937e6c12277784ad3eff5f0b 1656491907000000 1657096707000000 1720168707000000 1814776707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-42 \\x5e9c4602884a80ddb6da2752e479b5b445008225ef8a1fda5a41bac1063ee4bcedbd76d88de648096f399b31f8aedfc893a2ae705438d8a70bd8657ea7472298 1 0 \\x000000010000000000800003bc65083451c00ae15586c36b24b7122b60f1574a9d94d7c10ba5de8dfe36a8eccd66172b3101c0f0fdd91f7794f067de46907df0516d12bb25c908dabf8a7b132e2878d40392de2bd073ad273fd9968dabd7bbe166e975cb71ff91d8b83e4be1005f6ff2ed4a02e5a4220c84c5c228875d5f4fcb838d21dd9eb7f7fe964e87b9010001 \\x920dd848a42bd62897e8ad31b0558fc02cb6293ce719c2e7294f78321c6b6cf9a1c9d7624d2f02ecd17e6015b059c0df1457fb1c238f89c706d6cb34c9975a0a 1646819907000000 1647424707000000 1710496707000000 1805104707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-43 \\x62009a52e5f56163bd4a2d8d4743bb14f078b68d6883ba3498b568a34ab2dfe2c708aa1a8e7c0ceb45d992286b6e2fa3eafc786952a3a49872d405e7af22f12b 1 0 \\x000000010000000000800003b1f0a78c36f8145849444f64f30b6d0cbcc30cca6b9571c8bc114718d7fba2eec31fdf7e64905a81f5ffecc5737ccf9d53a70f53b7df6da033c3c8d2374740ad18f0d8d1ab655161df56a52fc8850e8af04bbca862fbcf3a9a9c304f09f50485207facfae44cdf379e405e3e2bd99f5c368727eb736716f02bbe8d7f2cb4fdab010001 \\x938e11bf19730ba0c32bf2142224660fd9df93423e4996d0983c7b63a676f8099314bf3b4eb458aa91d11b37406c73401a37bfb6e8747c011ec47c5eb8425e0c 1666768407000000 1667373207000000 1730445207000000 1825053207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-44 \\x62148e2ece40c42428d8e9a03e8c2573979609dfba8d6c5632021bf848a17b1d1a20b4b5811e9642b5ba1eba585e63491e3dee45caeb36fe2b8f7723dfc2902f 1 0 \\x000000010000000000800003c982e71ebe111db4de1912595bfb9aa76146b9aed2045f1b5b0ed87fec92192dce493cdc4e0613908b347d180bf6312430fdda38ff603d3a16d6e1ffa0267e7ca7ac43806e3e317a4710f1a363f653fa961c566d4dea804adba45c327ac8bf0f1b240c9c02e806f0e8a20eda6f3ef75cb0caecdb3cfc968a531eb423cfc72fd9010001 \\xdaf2d3f185f23f3ebbe4024b9acb175273d4003bb32d9520f12b8d3e6294087804026529a72b9e3429c4f0bd3b38f23a176de51b9bb97c59cd5f9f435fd0a606 1665559407000000 1666164207000000 1729236207000000 1823844207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-45 \\x66b4170427de16272fdc61cfda2abd6b02318fcbb19111cf72fc73fdc6b27f16289c9ca701bc237c6c38c5da4aa4cfed9c0598b1acfea5ca26c8e644a48ae0e3 1 0 \\x000000010000000000800003c2df7d3f92037690881fdb466f7c784aec577b98dce9c57489ab00323727b6590a186f41830bb38377503c42275fb4df8e2f358ef9a8607eef4b22f1d30ad20c54a424a190a5273d30631b0054ab97be4d1f0d257f533d25732d50c85b0b26a9a72db1b94eb55758203b8b02812b6dce042d893b3990862940917151cef0c2f1010001 \\xf3f0fb1d02b032f33d3099af4b22a9eaf4b6361fdd13fd956f285a20bd45357c4478afae8ec9badf216f923686f98ecca2a6939b07a60a704fd45b51c9d45607 1644401907000000 1645006707000000 1708078707000000 1802686707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-46 \\x69d437490c9eefa8e84074bcd4c28af62a3c88e5661f05bf105c0831e358fcbec0ba1a5286140b774b62e110c977ea1b79bdff772ae15c006b2ab69add7ea641 1 0 \\x000000010000000000800003e00e1f567a7d515539574b5dd3c24a2b500ec4aaddd9e9cdcfd9b47529d5c0bcf7096b0b5e69af0759e13c9ab9048652016c05bee54d6f317fca35d67988d7370bb92ef3d43d032386bbb8354e9f8fa870cef00abe4015f215fb75d4bcf6e0d924ccdd6ba92f0839467e3e45ade7b94e7e8da71c77a9a35423a20775737d4fed010001 \\x4ec130e57dfca647dac65e076f3f2b6923c474c44c2efdb78baf5cdc4b059841bd3c765bb56d84aae55509a3e27c1228555647697a123b5df5693c471d98440c 1669790907000000 1670395707000000 1733467707000000 1828075707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-47 \\x6a00bd5c69a7efce4aae1ad541a84e57acefd65832be417dca496e2e008c89d7ca439e56a94767dac14fc4464d13f988df4e03722ee7166643b5990c8128e1d5 1 0 \\x000000010000000000800003b20ddf8a9f42eb0e54bd6bde57c793fd98ccb65501049294ceab0f73788a98f8c27ccf1d5973319a8b7171cd237e28d553bc426c9ec648b6f523f8778fa71a1480009412ca3c9df4ede46e75741da42870cf018a482db1106e53f90b210143970e48400d4338cad9b00175c5254074f90d5988fe00f7089a6a1a93ef1a79ab19010001 \\x78bbc0a1823b57d4a5832da57ec4c075c54e6768b8c93e213f2664cd42c6e1937b0dbf2aad67191c7a723d4914faf9d8703c031cd5795b7e3ec0671e1297f20b 1641983907000000 1642588707000000 1705660707000000 1800268707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-48 \\x6a34262938beaaa738f26be5702dabee52788a6f86512ef3268b6db0277023a2cf8ca5b4d1816538f07a629d4aed6a5bef9b2ce5931b4c09f49b817cd59d5b01 1 0 \\x000000010000000000800003c7ac508dedbc74038a2537cccc381ea249fabd967edf597569be049bc95095fdcc368ae6c2e43819f9517755ec3fe8a7186a5c0a50e049cdb2058ea7274f6ee2ef5f8c71d550341a1d499905f682f4daaf2c23267199f654d4ca47961c3db049a0a40fb1dc9ef6217d99d3451d50ebef8b8c5da4555786b64f86bdba43c19e0d010001 \\x36e94bd301ce5250b8f8daaef52c5428126174cfac290a545104a7edf81b732a26ef7e5baeefdd4b6482ab313e326071c3dbba2ede41d51e29a4bf91f770fc04 1655282907000000 1655887707000000 1718959707000000 1813567707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-49 \\x6bd4cb0acb6db03952b2709ea63364f7d714b5115f071665d735b87502ccf558bf2ac3cb5850499c922ca182206c20fdc3668de99d1ed7fbbad313750bf19267 1 0 \\x000000010000000000800003f13b26b0d6601524d63cb1a2bedd5fae00318b7a7502e7ce8ba32010f3c1c1f4afe9228d2495a975ee2c63dd4b662d641f4f4682559855c12b27e494360a23bc2aebbc7be5968b2e876037d3ff27e945e0177d4485bf1a38af93a966ba4919c7b276815ed987bd51d80f6299a5c08529012e5706a99cab001586e3a6a0cd9731010001 \\x5cb86c174d7c0b38f434cfc10bdfb1854a11d2ab7c6299829571940620e6913eb7149099d937d46701f3fb4a6c51a7dd2e046c1492522ce2a8ad5c995f775807 1665559407000000 1666164207000000 1729236207000000 1823844207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-50 \\x71d095c4a52f9f1e148117cc20682892d8f962b9326cc2c2dd0f653925a2f213fe6b8805889911e6b12a2e039923c9d7215dddb2b114ec8b5951aae3b9242163 1 0 \\x000000010000000000800003b48f252f67e783a22acda50133d2076188fcbed66c86e419cad75d3e37d9bee2cc9199183300a301ca0a266119f1beccbbc47342ff635bf1dd3ce7ba81f8eae49dfb745de2b6e7677d13a5b21099ddaaad7dfa6aae9faaae8088483fbc3e4fcd8a077389fa23baa61813ab142915b4afa24acb20b85a30581780f2e465648469010001 \\x81231e2fc3a329a1a68ac604c99b82eae2da9fc03e2e9097bd5f4944cd8c02205df53a7a0aafb8d4ffb15802672f13091cb3d502c465f9f8ab7c96bbf3099407 1658909907000000 1659514707000000 1722586707000000 1817194707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-51 \\x71346595b726a62b10d89dc3436eed88f64eca47da9549ed7ccaa8af982be78d0562c8d0141cacb5304cf87d387324bb880fce967ef94049d9376cc5e8167f36 1 0 \\x000000010000000000800003d8584ba0bc3aab295f7cd29c8b839a9d4f3256c314ad64871e5936deaaf35ffa3923cfdca62b406c75b2a20d18bc32e490ce6e705fb5ce9faae043cdba62ef29efb8f763e89c3f6ef9b680391118574616dbf860074a094a2e56c64f5d3f11e106e6a047362c0d86fca88e6f163b5c3f019f9697e1687c561f7bf67d1adf1fdb010001 \\xf46f81cd33d76d9638715c20d0e4fc6f6355a1fe0db17d2671327afe8dee888f0a96001c1e60ee6fd58d397c0fbbb49dc7bfe0135bf3b5e6000326818490b108 1639565907000000 1640170707000000 1703242707000000 1797850707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-52 \\x748c65c29d1b1258b94dd44ffba9e9cddf854fdc1a362fd67caa691736c74c21d39c39d3088c76ba379899ddf99ca8c8bc84fb0673c1afe208270abd6cb2bf91 1 0 \\x000000010000000000800003f2f108eb97caf46df17f047a6b8a74e984cabf2c039d828f7268d3c906d746fd911ecc9e763a2147335a2b63591869219fb7bbbbe5f5fb991b02b8396fa0ffe9053cc0398927299e1661bdc26c703f598ccad6f31c641343dc7fb6277db09cbe05cf049fdcca9426e4880498d32cb3d7634741d2ed3208cb6a4efc543ed47fe5010001 \\x3950aa73fdc49541701840c677fffa211a157d3a33fd6e3603c6d052942002c08a598ba710407a108bc16fdbdfd40f679ec97f8fb50eff4311d7a0d3564a0f0c 1661932407000000 1662537207000000 1725609207000000 1820217207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-53 \\x74b00dca32e02bc9fe0961d9efd26bb78b0ddd9a4a23033a01c2cfb1d2009bac18858f7d8efe8701e95d6844d10198fd3648de93ca5f560dc53d5519d76b15ff 1 0 \\x000000010000000000800003cf0daa048886a3c5fd427c54a036a612cf90e95ee87562ced14c385afa56265a20f936bee723504b092aa8b5bd7a3125f35f0c6ac5184f5fda18322c3817b150cac75b03fd92a5e6993ae15002aec550cbd2b7ee13e63f008f02f1239ef76891c5e9eab1584df4635b78d6faf2638e25d4ab3263e2f184bf07d855ebdf4ce051010001 \\x2aafca874a6ff3a51dc452524a756cb8b235ef89baf3e53dccf423f6f2dd1cf009615d7093a720cbf029d4ec3c2e71a8b774c2a82bf8aa030bf18dbf4d9bd907 1643192907000000 1643797707000000 1706869707000000 1801477707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-54 \\x7670c1387d121cdca0c983994f52650aeab558c77b52ca34f74b2ab27b90afbae87f791e8340444900698ee9599205a920609a4123bd0739978a716aa01de511 1 0 \\x0000000100000000008000039305843d1e07dc5eb68d69aab96f0462e1cb87295df6dde1ca13261dff7ab8c2588f3b5626e9b8c6aa1600f77a2c79e89372fdfd1b9e7f2e2aa7ce9dc5add92cde3f179eca52eacd7e6d42e4264cb600c15f6a3e52c81d719de86a2aa70b5069f863d1babf0f6ce9e84cdd95999579b4822149d9752839abd626240afd271b4b010001 \\x5922d28b8ae9d03aa1d62b5df04639fcb89515196e0743b5794fdbc261aabdd9c0a2ac6b78b5b5ed09cf30945d38356771742845381b1958355e76f8d94f0501 1645006407000000 1645611207000000 1708683207000000 1803291207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-55 \\x76e8c4f0441f13c785f0ea7429b481d926fae0d06bb644fea78a1a92d0dd97beb438e66c8205e32dd30f8a1347241ea2153e51b6cb1cfa9ac6939b6e17764f43 1 0 \\x000000010000000000800003b36a05d1080015a4e0104eb18ee1e572936d46695218781d28709be85801241f3aad0cebd16bfd51a20917d1e017ba2e34a2033eb160950be5c5120f501cd1a535173eeae937b9c6c80c2fd761dc7745a9a5b96c9c2287e585f8d569bfc58ea9cfaab6d071af3e340e18eb4622ef514ff7f2a0769c384174ca19ceb28a62a61d010001 \\x78496816410a61ada88cac6a1c16591abe6881d93d27b00655db7ac9412e186b8e7a8a93c4e9c9e0a60cbea73f2d1463df7d7443210bea59f5b226b33cef1b08 1665559407000000 1666164207000000 1729236207000000 1823844207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-56 \\x78b41c2347a620e97040bdfe45a0f6b3ebf222b9e485dc880468679d7f172c8490e54389fd96c69605d16c7fd18b4140f7cb5f5450611bacbbd2f1187aceb8e7 1 0 \\x000000010000000000800003ba22bfac159267d3f7bad306d3f4bdb447c06ed310c30068102680a105bf290fcff03985267c5b7794bbaee52ef39a09cc404f02f6cc9eec15f7babe3aa98981ea3bea1c3a3c4704a2d905bfab4d052301f0dc72a53fd8699f90da6ad63b6a93a44f3b8e4d6ea5708daff9da6a4e0760fcba81bf60060ce1285ec05d0d48bba3010001 \\x67d8108f75f7262082db3da3e5893f2fdd0ec3488f78ec0dadc10d9be9c3234dcccceb8ffff621955de29824b3b5e4fa8e150cb34bd97de40cedc2d33ba30d09 1654073907000000 1654678707000000 1717750707000000 1812358707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-57 \\x7920681d220de67765ce401b6aaca24edcee13af9b7f7304c219ab2ce93e82437b7194ffdb15a2473f65e5b52c3cbeace9d29391d945a0e2506ecc971ff5563d 1 0 \\x000000010000000000800003b03dbb57b966cdf478cfd278c2c5a3b921dd7fc0baa7112b19a1968272fe72092f049036110b637fe7a0bee93a5f13851356dc08d1b238bdad468b624eb1b2164110c5daa3a5e9c09757a50d5d85923c821c9bbca1c7c70098e61bc624cf8e6084ee2a36bbe7ab45eff3e8cf1655be631155bce5b795d0eb7f9e32c33b10d3ed010001 \\xa4067152385013a8c1bb4638c320d741f8958f9771446595d922421ed3516575bd41de5f681b258946914ba15351f7f2899ba2be6e2a92dbf3a33bfcd929e70b 1648028907000000 1648633707000000 1711705707000000 1806313707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-58 \\x79bc0514fa18a75e0b63afd189cc1c1c9aa8574f9dee22b6984941e00a62b1e1ade73bc387483f16093a3ba5ae7e2a6e1c5e5aa1a4385a5754bb35d76200033a 1 0 \\x000000010000000000800003c95a852ee230873f4edf5b2093adf580ecbde1d1331a8e7bf46541ba4141f4b878d6575e8b3854b3270602d541de920e6538813f32060dabdcf3f28ad48a50d62976e8b6affae4c2b1c69fbdd258c7a6941e2bb6ee8cc87a2dc9ee5e334424ace9042b3750a6b41e61ed3e4d5e4c0bc6396906b8a01199ef173fbe4c33e7c13d010001 \\xe126e1ba45a252ffdd533bb76a91e87fb16cace102285d550a9d620f85792457c1c62cba391fb0f9299aa4a085a7baadedd68dfc1411f4f41de8cb254a4a9708 1662536907000000 1663141707000000 1726213707000000 1820821707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-59 \\x7a6435aaa6e144de146351add9a8afdccb3abdf17935a08940e252dbe4b0cfdeb4a4a61bfa20abebe38ad00b560818127a78eeda28e08d1d7588cd32a1a66643 1 0 \\x000000010000000000800003f6299a8d54a0b6864a4830057a44ea32cbdf3b1e54d4dcf17e425c14de41d6fd3a4ec25e4897a1024e6869437067993f85857dd5a666d105b2ef866cec1bee67e7de0324597a7853b28fb0470cb5dd3978b13fb7215d37684f88f878dd0c89d9c5e8e8a00f49e47eef560e4ba1a647fd172b26fe7abd1cc0d6ad144bd45861cd010001 \\xbb18fd96a3d94946986a83a19d705fa85dbc10eceeb3f0a7dbfd39782c740cc980d5aef1ec974e9952dcaf35215d1cc619f42da99398c516143eeacea4d42708 1652864907000000 1653469707000000 1716541707000000 1811149707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-60 \\x7ca840231d9b8b3a79430e6cf4c18852a551f7af0b715db106aefb1024bbf1a2f52b0a2c55472574f30a6aac2fce4052d0d7782ebaa7cea43563c6dab54a24b2 1 0 \\x000000010000000000800003c06ee06b2c2cc562ef7589241b49952e4fe75862686627cf28da38812184b2381e60dc69c3c73669b69c031f9e6ebdcbc2b21319fd806cf1d0d03ce732eff5c20df4e2ca0123c64565ccb0e4389b14e64f63023fd482dc5eeadc77c0012a152d17c81c5626cc521b0085126ab82243e0ef7c5062fb108d55cc7f7594fec8e7a1010001 \\x7ad6f052dcc9c6f9ade92361b35a68acf7c63c689b19f80fab574886034b55a883ac80bb38f055c942afae6b968cdd5dfeb7c849f320911e9c8b99316b6df20f 1663745907000000 1664350707000000 1727422707000000 1822030707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-61 \\x835cf62da2ae3d296ae00ceedb1998c73d09a0f95cb38e4f88fe8fed4f69a293d9f7f719a72033bcf7ca8ee09efe0b83902e49d0a6ba68869552def183c05897 1 0 \\x000000010000000000800003cdcc6085823a2554cee19179db82958d8baba018cc9595b356d22d1cbd4733b1ec51ed0e4c15fa952ce5e9adaf7d7b2ad2b990418264e4d03180fdf6aaa71f1c0e8c42e19fd544d3d0799f4b69eb07844586095266ff7c9b17f049892536db4a28523a5832afbcbb77a065b413f1a5953f946237b2d58caeb819cb5c790ea597010001 \\x66f4f3fa72a2597b1972380e40db0c969cc2acc8bc071980a1ed8f8eb1aa875bfe9b50c88f6ea037535acd5ab383ace162bd9a34290f4b04a8e358350e2e980a 1657700907000000 1658305707000000 1721377707000000 1815985707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-62 \\x833c224b510c3751dc32c1ea8afc41daa86ec09f65b6d7b7b409a1ece04e8795aefc67b6aa890a47e3599585912f86a80fec3876855de180e4734d5b2d561b78 1 0 \\x000000010000000000800003b157d3c02f098231ca75ddc0f0e0033d2d442b9e0ebcaecf3c2c88f8cc04d957f1591f153830787b8dfbbb4c1b3c05c1f7b87d210f67a198a32edaa0b34aa8d0fa2aea0d6a624c1f72f8bb8be77c12a9e8f1e88f722683eba8b4a1a7e16feae398ebda3f10adb949806705a4b2ed7dcde70fdbed222482e83c02364677a0f07d010001 \\xd63f39e77df1144f0d5ddf9f47185eb3e07b89d9d1b98faccaecc2763bb8d44cbfeee813505134fe29c5b60fbe68692d1935ada8ec524562c1d1e3479dfc0701 1643192907000000 1643797707000000 1706869707000000 1801477707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-63 \\x849864f251cccf947fc3eb1519fc90af18d85890fa357d718858bea4998a9bb6cac7d28d01a137fc7ba94e76034480b12675b8e637dc4725863a1294fb4dcd42 1 0 \\x000000010000000000800003d341e0e096a97de98cb19ee57d985aa55f7ff3ef55336efd70e14501056cb852d27d20095406944d4cf94ba4c4c2215ab3fee0fe2c39363bf0b5bf98143aa5154d74dcc0118a5f19efdee018353228bea35bc39aabd73e87d7cda19758d91a6fa129c120a208ce8e71e90e56516c279edfb3418bd6580b06bcd928f6aae514d3010001 \\x0a0f12dec446fe32821302caa28b293245f6ae68f7a5c020390a810972d09d571a74544428c08ec8eb6d216cff483ef961b121716e690a763eb930ad7593d701 1657096407000000 1657701207000000 1720773207000000 1815381207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-64 \\x86e4d14d122f18742644ec85a111f5e480ffc9d2e583419821f52a89816b2f8fc889a2f9067d0d004befe4508ec600867b18d82349a368f297c179aa4cd0d85d 1 0 \\x000000010000000000800003d0788c4b45f06e032f858f90e7cb6a65996a93cccd009d5e9eafb90b9d70f3669f735285aed1c2c33b954510fd7fa818ddffbed265bbb3bfba52fc5f98a93f5b8a782555a970034a5bf8bfd170a250c0576a17cdd6c97e7e5867ce3ada8e37929ba8abc14d7cf6e166b6d538efc3fe40daa28bd6e0a58a158aefb51aea57964d010001 \\x04da9211332f37774a4c0df3f89aba72fd05c03a63a7ff92e8bfec8a4ee0aa919be3ea170a59b018964d067c79b6adef31e5a3b222314146020a77d4b4c87205 1646215407000000 1646820207000000 1709892207000000 1804500207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-65 \\x866493637036d2c51d8d1132d70d24f9fc44805af9ab5fff2ea559a2fffbf5516d0a13701adacd6399addb4bd8c2e52543920c7700651f8d5f85e9641a98b690 1 0 \\x000000010000000000800003bd61183bed377a2339d3dff3908167e550a1594a26ca4080188f60b507a0729e7fa6b5583f84d7f1301fe49d069310293dc625557c168b1bbbd0dea655eef038ed7b57c5786c9cc245a7f270aed05ae41ce34e46a9bd4eb0c458078df65f8f763e1fa656418f04c25344ae49cc9a14994e2399821788a2c1fb017d72508059ab010001 \\x4ecdb07a684e2a0e87981ddfd5122f5e8da898f485ce90c08ab44f051bafab27235894873b90b9447fd21c7d7b49d92aa567dd0b175d0c2136d011b031540b0d 1642588407000000 1643193207000000 1706265207000000 1800873207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-66 \\x8768627e8ac99c2c0973af737d9f56234c2d66eeda5c919846df02738a89314673580d18befb3c3b719de5203c0c41bb767da3b0b3516c0575fcaa49059b0335 1 0 \\x000000010000000000800003aa1281b825eaadd71cfd6f0a5eb96caad4b25e6d19af482295c5d15c89e8aee22253221c70e88b23261e816b3d7cad3136210d308ffef4a092d92a2e112c6e6c93e872faf50968680ae980f09bb2a76d276059e01ea25fef1625ee08de1b80496c46f0e586525f53d1386c69c3e0ca3d43dd303c5c6d8d9da7e92be3a661c23f010001 \\x92b8c0cf081d4e2ce8dd3274a550a24c16e96d24140bcdc40b9fdbe90a76d4820d2ed195ea5eb8f21a7c29c56d5496f64a75d923aa90ebec42aed0750f32f703 1666768407000000 1667373207000000 1730445207000000 1825053207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-67 \\x890cfc94ec3ea98af1e67d6458d9909fc7f4b836ed351ebb293144c4f62fdfd3f5999cb207d85ab6b70447ef01ff7b671c34f8e47ad0f3e26e4ba825a229b47b 1 0 \\x000000010000000000800003a167614df21a6f121062f62431ca89259745d4147b2c6cde459922f08d48fccbd5253996052f9d2a1428488fb052e29ce2e6180f42215c8866005b3eb9fcf7cf5f0b0ff3c70ccc3ad8963ed34d8c27a7b553681e9bf7bd22845f945211f7a69dfb2e12d9c7e1ffbb8b437b54e721e0f65e29d9abf4f07b052f7db660eba78c83010001 \\x259efdffd90f57b2b3635a23d3633f591e90e007ebcd0331f0f7aee510a86ac58037f74fa0c30a76baf2abe299a2309ddd549116c74b9537595a3d9a90e14508 1647424407000000 1648029207000000 1711101207000000 1805709207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-68 \\x8e3cbc14b02186cb82a5b63c4a00311c978b754217881a16c984cf9c12f47d5dc5463b95396335d0a580c03fb213bb9591208ee4528bb1043760ef9e605f9b8c 1 0 \\x000000010000000000800003d7e5e31554382453e69c6ed9c21abb79ffe22d0b1795bbc17edee32c1b3aaf8cbbd275eb58272ab2a788684c0b79c17883e4f66e3f7cedd7c639f722d926ff8b3c9c7918c77110fd0c72644b761dae7e2e334f72e63a375c67e2d3a0617211577d1fadff6c5f4258f8e270cd91376f2587fa71ccd7b58af7118e16bfab13e925010001 \\x600fda08915faa837e4c060beabfe3547d308f516c9a98851de729681a56f1e441efff225081114f054f564fe2bd1d8abccdfc2a02f036a33c88550fe1cbf20f 1667977407000000 1668582207000000 1731654207000000 1826262207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-69 \\x8fbc94403b7006a569d29300383e5d8340c045acfd5911b7ac6616908e745855ce2afa1bfda16f1a7985dc5fabd35de079e65cc55a8c421225dfeb942c49a8f1 1 0 \\x000000010000000000800003cc71636781572d3625fdce1a68ed2718be73fed2b52df72d627dbe1a2f719a79c6ada5c448535dafcdf2bcddabdb0be8694b9bce76cb4ec832d3b38c106ba90ac2e7757137bf1303bbc9196643782c719644cca9c7baf9882f733e9fdf121fc61b42217948385b409af9c80b6a5e589818b5b8da3ca55e9404a98ab6b283a451010001 \\x982803ba2b63c408c4a536051131ba5671e53224d2da5f62dc7c9d6f035232213bcd193c47450317ef2a2815646e0c088cd6f76aef4aca158b72b4807fea6a08 1665559407000000 1666164207000000 1729236207000000 1823844207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-70 \\x9224aec00d941ed75687b970313b4f33861d293796ccd682b10555c940b33f72a04ac6fa7e2f943b39398d4698cbe1a029a9dca446e1ef6ceea5f76717b680a1 1 0 \\x000000010000000000800003d4564d8a9125ce998a5b13e829e857aa9bde5c889fe03bdd5986398080dea64d2a091cbea155da64f860e3b804d00e8494a4efe031befd6cc9ae3f512e96da2a2885e3e35572e2aab7937502a37a04d8f831fdeaa14c1f7d8ce94f99a7ffb1575d1a26c5499a2571c3a289f8613c2a55e0777c6697051e0946d6c6b6c0aef6b5010001 \\x115a8974747a77e03def0d6c65c763e21208a21ed50fe4ba4241181720225590bdd7447bba9374070c912befc8fb800c978e594d34772d08dd274c3a3ff73501 1666163907000000 1666768707000000 1729840707000000 1824448707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-71 \\x9488b7b495ccce71d2ad451cee889c78aba188aec463b8d96ea6d6310311ec91ba1c5caf8a38248029fb160f0117df3666c0c8516478774c747f6c880f1e219f 1 0 \\x000000010000000000800003d61b37ed55d51036bb277684117589fb885aa8b4c508313c4f7bab6308e1f9194fbea7217ca8fc30440c8091505e38f07684d45356804f563c491b2ed4ab977c7e6e60fdd216b24c469fad49485a47e4a822b16c0d40f16b6109ab927f1765399a21a9f38deeef69aba43935ed51b73ad99f11a1dd42ee28e92efea76ff0bd21010001 \\xd3fd40cdf3954d0ae3171cea5081245740c5f44f0f700df1e178b747e8f6743089201fbbf32697a44ceeb1f8494acc235e991844f0729924755aecf75cff4403 1654073907000000 1654678707000000 1717750707000000 1812358707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-72 \\x95b0ee54853845c3c6e92c66da4ad6cf1ef797d719a3de8f719d5e4e03cb53b3ec7c8d9aa5ad2c1c05dac022c02a52aef3770f8012b9dd9338f3d1db2d566f85 1 0 \\x000000010000000000800003d1500c5ba1a47e40b752fb3d5b91befcd7eb8c7a38a1967227d563b9c9b2b8fed7952c969615c8b4e42a78c5ac89eab0458ebb3552652cea25dd3e8c8ab663d88a7b82f0c7f22344ee0766791393c9907a70476fd372b4517304dcd1cc885a07e73ff4852014a92c892d5b9a66dd217ea136a943c85b1a28834921fcbbd4e0ad010001 \\x74ae5d015794c947d83ea95837c0c8e13c3f9560d7c240a00c01dfd6356c05e9bfc200c2e8e3af061cd7997adc8af198b63c3465dfa0f534d884a7655aa3c902 1638961407000000 1639566207000000 1702638207000000 1797246207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-73 \\x9584e8f117423c2f97cbaa68c13918969187913ea666c63a2ca0cb207d5524bc608e9adc436bb6862c63c6f01d929645d9227c8b88b8954bbb002152993206a7 1 0 \\x000000010000000000800003eb5197761d7ba0812e9a7286d6fe1d2bb7db0a10cc144a842c135a71f56fe245274502954f6a43a97bb35968f32f394bac31c39e1740c52bd3f6bf05a523805a0c7176c65b1164bebd641544b6e201513317015a21b552b379ad507f6dc53f807a615bdeaf643d85664dd09430a1c419dbbd946c9c762bb2098c8dcf2a322ad5010001 \\xd0cdd9491ec861028d8942f67daba4a1a75558eb76b26d9f252fe24327fabc065cba1b9bb634ba50e1cd4d5f485db3296922914ea26e96cbaa581043a1ac500a 1646215407000000 1646820207000000 1709892207000000 1804500207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-74 \\x98ac7a696cafa00500ee00f22d10243081460b283a69823e328e49ede0c7daa01f0c19cccdd6ba86f3c2e934147b1f373755038442872b4d0714563b95b0a524 1 0 \\x000000010000000000800003c582e0e2c82269fa3857134b9fd24d1e4f44eb0e302c3b1eaadb630c67911eb5abf9eaacee3eb1f019577262ec7e2fbecf63fe1183c97f6d46981d3f136eec82697604bea9bbebc741e063bbe8bee008d13ff7be461edbef93de7ce8616f8ce4ffb8088799c336871087e928daff5d57d3013e9f731d225979160fab87c4d385010001 \\x3984c31c0eb74bae221236e47ee5971e3423ddaa9b1d22ee516101c61c04012bf2fa0b54923054bb5903a5b2f7ce973c17cbd2463b9ecddafad7051974de2b09 1651655907000000 1652260707000000 1715332707000000 1809940707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-75 \\x9a80c2c0873e2e12baf1945948876e75b0d826b17d08ae06d9cec3cee0706c6d9b36c36f812a22433da4f1370131028b8212d25fe169bf75f5101c07a60b9a3b 1 0 \\x000000010000000000800003c69a1ae2cc601c9a94c0c839f5b32ba8b0bc77c3b3b908b6f27c219169a1803c0e23a36de6824afc5cff75dbfc35a8f201f1f7c6af21315e84e377782a59adec6968bdbed220c6437cdf76f67e74bb87dfdcd5e8d0e1cd5c4cec1860a6db3ef5d38f02b108a9e27f7b66992b5a96b7d3ba9390090a2aacf3fe656cd9403f22af010001 \\xbc1ef06ff56e5ef471f411be6d8c93ae43dbe0a7a5f0b06cec4f1e59d66c45f93dde60595d40be13d7e6d1804fb0a63a49fd1f7cf5e5f40c4f0f73f715526808 1653469407000000 1654074207000000 1717146207000000 1811754207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-76 \\x9cc8afb2f9b5a78017fa89f10c069457893a3a301b94d1e5281ba0d08b95a3e67b76ea2f3aaa23be8945ece91ce0ae5008202487a5ba164d6b009de4ee40bc1c 1 0 \\x000000010000000000800003d56eb35bd6be96bf148cde4412a4fd6647dbcc8f441c5c689274021a1ceaebf178ad01aa8302b2f63e5f646519acab406666ac35f62a805cd7bbe98be2a39f232cf7efe895f7d3e57b1f3b9b7f57a48029f01fa5d2b3fa2c83cb812a96a3719fc6fc241a1e3aed0109c475ec75bf6f5be53d432422a881230ca03b814dee3c61010001 \\xd49c7374a39c3ea128e5239b50f621e1b5fe00c58b3b41945377e92e11e770d949b738d1b9a5b7ef60aa038c338dc9f4161553ce6b8fc385c521ec6fac62eb0e 1658909907000000 1659514707000000 1722586707000000 1817194707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-77 \\x9e44b8f23d90e58669418789abb37676c0d9642e0c3e0685e3d0b5ec7619246cee4f380cd5e225a68b995802dab3f7125100d0260ccefbed932d1bd112f74f15 1 0 \\x000000010000000000800003d8944e06d5bc6e9cb6a7f947f0b2ff7820856582dec62e01709a41184179859bde8a66b54a8395dd068c465e8b7cef5a3bf3cf64822f046611046af84f26f0e8897aab8d7d49008d542dd0c5194a4b13698db49dfc585ff9d1909154d95da893289f754fedad0bb35789a9931daebcff4933daafc233e44bf06ab43f57a1d2c5010001 \\x7fbfd6c80c33105f591a73bd64c3da344755ed6321986e737d7d24c8a60fc189888e17d5774c4b20c04bd0905739763431677f51e3fa57d776858e5473c26401 1665559407000000 1666164207000000 1729236207000000 1823844207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-78 \\x9eb4daaa65acf1d91321b99236ac53dba73bfaa4673ec518f9c29b0d3048f96c5e39eaa1861edcc63c7a205eb2d90f979e5bc9ca1216ea32d8888af8b2f94f33 1 0 \\x000000010000000000800003c52624d7eb4f5f7eca6334d7052e0a2c602f157ffbd4ae809d993e6d7ce7e7c45025cebf2bc21cfce1447edc97e6258b94b90e093743d54fb4afd831d084462baa58c811f35474f35ae86701fa550ad67f59ec6e543055833ccfcdfc024b393d46f87d18a2b059275b47c651e145df86748fe04d294d475484ec5a0ce8bccf3f010001 \\x2f47a1d45ad88a22d84cb416dc89fd1a93239e5a57afd061b74b831851fa497b89cbc6037c90e66e04c1a09e60e785f8619c995997eada1bd1e8fd28d2608a06 1657700907000000 1658305707000000 1721377707000000 1815985707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-79 \\x9fe80dddafcbb04562031f8f0536414c8ab4ea000a76d0b62f34b93f3c4441985b51273f9661c968df1016480ecc1f33a5aa2df9837b86bb9c20980176e135d1 1 0 \\x000000010000000000800003b48d5e8e025aca7a55181daabc7e87d18cb47348c176d27189d5fc2bc2bff823b44300db7dda01a9634498abff771bb8da6233c16c3eefb9895dcafdbdba663638ef13de2f1e8dc2232cdb203f57a7e4287faefecdeffcd3f8ce5a920daa31e36e059ca874c060c7576e134f366d9093cfee41058ee80d1d52832470c520026b010001 \\x7b32bb1031c099adb52cf042fb9de49ce4bebfa00c13f010d96f4c6200101e8174fdcc62ae717ac5e082c2afa60171d985f5065ff10ffaa411135b9a04bcee0e 1652260407000000 1652865207000000 1715937207000000 1810545207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-80 \\x9f8c9e5d1d0a7900875ce5820461be6168d6b6d949c9a23a762ef6045e2961db53383ee4bfcf58351695d63fa3c67f1501a0f151d085bc2770b6a048eb7cd40a 1 0 \\x000000010000000000800003d1026c1341ba31ea966ee83305fc829a5e6e265f8099e3742a40f11fecc83cf167150b06966df6e23693a9ace1ee1c0cd2dfc0c6023b89fb92babf11580f8f21a4ca1ecef547f10ef75c3296ae4f288dbdef005a24801668e2ff3e661cb8437c27d9e2379f0ae91c59618da03f07180c3794bee3a0858654143fb767d7d30a45010001 \\x493060634f96cfe583ab668f8da57ce759a9070760a779f0939f144fa15859bcf68e40affc7b5a2fd4a130ab3a248bf2816d320d23f5bd4b6324d2868836a80d 1669186407000000 1669791207000000 1732863207000000 1827471207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-81 \\xa058d5d0fd8dcb19dcf309cc1180313a36dcee21337bf4d0e1d6d3b352da228017c35e454da1b7bdd085da48b4b38dededdc08125189182de9f8380edb2a0308 1 0 \\x000000010000000000800003afd625285a320a7e93aebe1702b3292983de1cbabbe43194558d0da3552d2c257f7a9af13eb42ea3f08bd2c6aaa4e06bca39f8f6680cd0da31da9c3cd116089ab580b676fb3a44bbaa3180e68ad5515b75d490f17f55f1738f332c0820fbdad59e086ebad815444d9c1788333d2c4306d67a7ac8542dee3c5c603ade09e53c5d010001 \\xaa8ef0ed431c6a026979c26e6bc00e8723ac36fb52ba1ba548e8b70983e7904ba5737987ff5b0fe990326e81f287fab4e47c3d03d42db14ed2567108e968920c 1660723407000000 1661328207000000 1724400207000000 1819008207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-82 \\xa000a4c20b8414d9c6563108ac9d9c4cc3fb32945414ca3588bc955ca48d5fe6d518aa67db79d5d3d9e888a0288d5c9ccf125e7a5fe4a0d9936944532c2e21f0 1 0 \\x000000010000000000800003be29c7381a66b3318557302471b648b90d7191f555f1ebab2a113fe560a5d5bbbd5cd49d069e23007940a9e00f2878cc3722b9ac4cdc18124185f867725dd41d89e683b42e7edac8eae641377a151b9c1cf6965191b9518e647294cfd16488b55599efa1d71c7b0a34e15d144e6c015c36eadcf95f54fc93eedd051cadb00d83010001 \\xa05b997c66c5085c48f3888753780189a7b00781799bb0aed8a23415731f32d664a754a8a8b5078193076f0db00ecae866d1581502b00dfae2e2ef258044030f 1640774907000000 1641379707000000 1704451707000000 1799059707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-83 \\xa274ea8848e7251f61a2b806ffa98c47ca37e3d2aed8eb739dcd693a55cb70a62c0acacd18aaea347bc14506864aa1a2fc4c2de34faa8a439d44e26e1dd54fd6 1 0 \\x000000010000000000800003abea1c7b0aac9dcfbe75ab32dc4f573c29a5365b303e7bed9418ef2934bffa9f77109e93ff4a981a7f1b385a8aa2591cfbbd27bcf83b008f634877bb4228370988a1a4c524c196cd56cae924ca54d3e61223fc9ae48e29adfe9afa693ddc951fee210489fd3f696f0ade5ea5a07e153a79d9b3c783c2591c2d1a9cf6becd0e37010001 \\xe5224ce4b9c3e390d616ada87de6b78a400cb3df813e39260144aaf0fdb19325a7c26c7d32e0e15523c206f86051b1b380b338e504b559ada9b8666d49439206 1639565907000000 1640170707000000 1703242707000000 1797850707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-84 \\xa424d4e23dcde17d78d8aba886252f4219d173b88975f7957cef32c8e4a0a53dd7973cc32e2e95d03774f400478599ae8c031abd09ca0e986d6f6d23f6b09153 1 0 \\x0000000100000000008000039d2dcd7ec0b91398ac731bf4f3b9028e9c0614547b2d46f72555a0bf4df1a64ca91a2962af56cca0e544707da344aae148e49d09879a7a229d5c1d6b7dde31415ea3d23b21baf840f00b8642a25469b40059fe0e316a1ac929394ef38bc3863b88df577733956316bf70dae7e02e79d7061f45161465514cdab44339ceacbf07010001 \\x6768ce53bea2b1a29a080a94b3054c2fabe6801e0be43f8575c10ee64ba53075ffaf54c2e22dda7936e798458dc63da1e2b147880c5cb6dfab0ac5f4564c9505 1648633407000000 1649238207000000 1712310207000000 1806918207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-85 \\xa9043c4c0fa2dd81981004ece85b94dc08576cd1e1a440252c03cd635541fd467dc149069012cce1a80e2843fbe9e4aa357e8782a57a2bb9d1fb1074741d32cf 1 0 \\x000000010000000000800003abbe2b200cdf5f6ee10d89dfd6a7a88f67445ee51268c72b09b4f01d291ed911f3161a987cd65b82ab9dd59c4a75b9b30dad8f52295b904834f9599e619d48d8e4fdf7bf8470145470e695194d52a970ad4174c1c6eefc6d767aaba39ce4e78315be005499baa7dac787a4cc7e185cc36880e11db3158fc50e2fd7164d86bec9010001 \\x5dd45ccab4f518c2802ac254ab49d0c8c2fd61110bd27c4bbea842731737e518a98748e0a9dceda8f3cf689438eb9d1a6387149acf423e1f8070d6a43739850a 1664350407000000 1664955207000000 1728027207000000 1822635207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-86 \\xa91c95477bd7536ec61fdfa3651b7ecb75b2a3d54d8d28fa12539e3ae0cc14ed928cf574144df720d1f55dd7bdbba3c6c53576a88c9f169c9e2d47ac6c1fea5d 1 0 \\x000000010000000000800003b4598b5618bbc006db89871855fef23aa912a4c3afc0c080b5fed805967f7dc029fa82959b2dbe6ed580eefd712fc2ca3e91bee85ef0493f2424cec0fc555e162b9d0a17b7bcddff172fbaede84cbe8f70c46383d7b63e26eafcb544b7d7fbcaae26a3cb6a581134bf31c8f04669cfd5a1040c2697e6827d118762cfebf9462d010001 \\x37b3ce7bc5fc39de6e14f24ab5980dcb72b0533f36383ea18cd89bd11131b3db7b047407d2313414473fd7037188ce6b84ba96bee577f2bcf7cc702c155b440a 1656491907000000 1657096707000000 1720168707000000 1814776707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-87 \\xab248b8dc5f72b96d9624aebb0d10172ffb66bc261cf9e3f522ffc9531fa681d02ac073ab0c467c844306f1157799e6b827343560cee153ac01a6f0d95d4f9ca 1 0 \\x000000010000000000800003acc788a808153aa80972470dc1e3ff35af67005338cbf97e0cfe005dee4af3e4d2a23c660d01fb4b310f442a8a9cf18b2a9a21ea9e758825182ed3d882f07e7d62cae0a6ed6efe002520aed6ffeb5e1b978544ab3154e79d67dbfb8d6721f65be89c7f778e375079a70cecd84eed83c0af20cf98518d0243c9ae24b233f66be5010001 \\xd9cd3f0647bc286a1791f701eb28c55c2110b24aad226e950b9e8be793f03ff5e7050b8d188e57b05c8cbddfc892d35f3742b26e9806bd45dd66945f6a35ce0b 1648633407000000 1649238207000000 1712310207000000 1806918207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-88 \\xaeacbdf6353969c1e66369e4d30e256c628346a7c4b2694c42dee7e92061dded8d6588b361ca5e259a43fb893020f7f0c43fbbacc68aa0e115c3ea10cfaacedd 1 0 \\x000000010000000000800003c5b17e7533658a5819861d77623d6118b1d46943458268d9db458d5976f4133fec053b7f018d98d7cece2dd9460754887d2a8a6c26d87a71d28054ebb27c705d96034dec9293471c00270b78c44cab35d4653299b659bda5cf0882d058f8eae1c087bd6f7a795053e9fa669b55a429aa77380abcaa852015b416930e35b447d1010001 \\xba1fd0c1c26195f7080774ec03778e8955551803f56c8259d1f24bdc26bf99b9e5491312d716890b88096bff6001e35a22b7c59775d5498edf07249fc1718a07 1665559407000000 1666164207000000 1729236207000000 1823844207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-89 \\xafd0cb9c7c127a9c67fb3602ac9f6301517668cece49d9e085869894597ac0253241d0bee3d0bf4ca94e6fd78cd927ff840fccb8606be253cf3ea5a0d6738c24 1 0 \\x000000010000000000800003a6fbf8d8679456408e7a34983798a3e841077a429aa0e058f4c70c175ff35d2bab6a60d5d0cccaa8c28d3459102f570ff6a8726ce1c084c242d8f2e7e4acbeedf97b8774f4bd71ef0fa2c7ee06dbc14fd3ab7da37b349f1ce32060eb9f6eb3ba1cd300e28b3c2025f811a856f62d6380e632baec55cdf4ec4b21d72c5bde0289010001 \\xb8191933adb74c5a657dce08d76ce1fa03724b4f6577f5d3f8a94b242d446099778f11367adf0354bf6c4083d6146c6da1eb37fc492f8ca7cd6a19de24aaaf00 1648633407000000 1649238207000000 1712310207000000 1806918207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-90 \\xb2c86011b894dc18e26e7aaf34d180da225e41ff878377ff870cb4c39bccc5d75de50cd4b4c2bbfa65ce6c267cec1303ccff4a188289c050e4fcd4302bba2c00 1 0 \\x000000010000000000800003ed6503de5f01a13163987f073cd9efce9177177259c4a8261beda3383bd08860b135f6b06822a25ba6b4b5d4f9af540a869dff1c20e3bc170b400fdde1dcd9a8c0289fef7c691b75e3451f945078a39431f6a410d5477fc520f464bbfd8ae44e5417f295011f2af56307ac52a39ca20a06fae1d8649861f740e1f71164b5cd0f010001 \\x184fbcabaae6aa90f1d130d395f3fbafa27ccc192f18ea6d5f0d3689f884942fadfbd3926f2967e153416c28b9e00d651dfcb980126fb53ffdb44820e67b6100 1655282907000000 1655887707000000 1718959707000000 1813567707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-91 \\xb4401877e6e614157dd0833cd2f0610c0a76c0317254ce3ed898f65de8f795f67cc8ac1aa2f703e0e554a5a97515e6babafe9c7d75d21458309c373dd6e0d0d8 1 0 \\x000000010000000000800003d28a6a41e0fdce29574de023e2377a56aafaed1ca30f98aa58c050f76c474258707c33695faed58cb61e58ff39439d82ab249e7d46a641f65faad131c3bd346edc75056d4be4d39cee02429dd2dbefad22289af499bdedf147c2cdf464191dd348bb5901b42553ce76c76b7e802b82ef4b41ff1f58882ca25bb406732188315d010001 \\xcb016e1faeb6e753eb99e829fe83bdbbfa481d638c5986527bae40ea10815ae512f7782cb31f96ae8985982e4ea72bf6f729299632154ea49258adde93f2a30d 1640170407000000 1640775207000000 1703847207000000 1798455207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-92 \\xb570cec8a19c329398e45c4ae17b059a0d1f04c2bda778038c0f88fdba6e8b458502c99c7f8e0ba98fb71eafecc6359590e5af937a310d6bf41240200bc67733 1 0 \\x000000010000000000800003bc63ccd8a158dc34ca09b612d5a40e27e3ef38a0bd004b84bf8a1a8ec36ad1e9ffdd1d745c465645fa73d74c23e1b29fd2b8f140ad7fe292feb17998ff8fc667d81e37f57dfea93d0d78c0cf192fc56006609c7f94b6b1e7dd8153230230d571fcd7eebb67946688c74395900e275c21a8bc799ba468582a33d84fa5d1c9b3d9010001 \\x24be91707ae1390f27b5a7fc9d525c88bf38cf3b49856a894e9ca4548043e4819abc8db525102db0819512377810b0dfb2bd4ba36985e0a46992d46685b7d609 1660723407000000 1661328207000000 1724400207000000 1819008207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-93 \\xb800400c04ccd788653279a7a47a3b598d55ef1131dd1750deb720a688a28e341543b04ea59a09f45d89ec48671430a833075d01c47a744e848212fe7e594cbe 1 0 \\x000000010000000000800003ab744ffe0ed15329ed8af4ae42b893fc19093f58347cff63f7b279f538433b2d683c324ee56a4b9d7cbe7f1afdd6a90aea5bbc1d0cf490b9a7d1ff1b3365b8eb41767fb2f7519c7fb41d3efba075d281a5a600c0fe3f9229e6d83aa64c9d1625aae2aeb312ab81206cc4159c525752fb98a365e3ccdd7d947249f464d44865a3010001 \\x599e93ee71851ccbabd6e2d989aa6b38576716be190ed82ac89982e2b14279d1ddaf5adc5cb7930ec0c78aa8e9392cf211e740f7aec4edf29befede41ed39906 1658305407000000 1658910207000000 1721982207000000 1816590207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-94 \\xb878f75296a69a512dc938bce3cdd582feb2003d18421581a928b897851827dcc5c6d8d5e75f5e9c298c6c816473bbffc941bc053f668e2c99c9c0373d9fe976 1 0 \\x000000010000000000800003e5bdb41f03655a8bae8174a7371efbe963e791358d8091e91bef9be717045662f73ed6b7c11b47901fbea2db638600c77da6984ce689cc90d4f0c2dcc5ac6609d17a6fedca1ac63b0b624a33b6c1d8d8a032afbea988d76339cf96cdd954d49c739070f7ed111137e28fecfc818e27115dfe4d1086264bf08e7dc820f9a4f3bd010001 \\x5a745ee97c8cb444563849700b4149a165c70697cd6ffed639e575a1f29f5b851e278cb1fac23afad8d7fe7358eecaf89e54027a0f8fb5a67a5e8f771f545c09 1664350407000000 1664955207000000 1728027207000000 1822635207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-95 \\xbbb46b920f873997991d06b93ffb6801403081ac542ea221ff5845ea893bb95a08f39598d6aec6db639c9d6d57f60138dd35cc92e44bee2d36b8dcc1bea92878 1 0 \\x000000010000000000800003b7242f9f9e0d696d8b011f10ff320a65d778ebd9885800c0a7475dd5e0131ee41d4b5ecae13ef197f4ab9c3bbf8b2c2d4176fdaae4565172edf2373f305899e52cdcb8dfb8acf46fbec01a6d08ca72e3b26740635da0ba44ecd43048bc465c29b241042051902b5aaf73ec399a5076a275bfa43468a3fcf460851386d388e30d010001 \\xfa1d9803ebe311233a1ccbebd85fcfaf7e85de605bf39062e8872acbdd7f07a700b310c17ceb9b349c640a966adbc336d23920a23812325b7314a790dd3f3100 1654678407000000 1655283207000000 1718355207000000 1812963207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-96 \\xbcbcb595ae09cf4b7b3ff04a7bf45da8604c9c26ceb0a98aaf4a48d6737fcd065b6d0c268cf4d153de3bac7015c7df4de1961af32ef5a2290284225ebc8722d8 1 0 \\x000000010000000000800003c1a20ffc8037b3e0fb692adb3da9fd7010fac3faefd374e60ae26dc2d412ea4f303b323c2779f143fbfda1b72ee3a9210b2ac24750782cf7bae22008cfb03b329d6dbcf38ab77b64ded43ccc1d29fb2b35de5e7c98b225c86ad4248c49c9f646d5e2ec585a27239ebc388afabc5a12eb76279a4035737bc935d2a27fe0932fbd010001 \\x04eaa4eceb7148c28c7cad9dead8d5491f6f56dba566e3e28a0a7ca70439d994f761062c42c81c61e0140b9784fac976fe7139e98cca424e2e9edee76ae52201 1644401907000000 1645006707000000 1708078707000000 1802686707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-97 \\xc2f8b743e2cefa52c18caa0ca4df13fb9de89c7f583081aca581a588b50fe36d6af3f3823b05dc01138a7244526eab47f57e260c747ce47580d464ebb1cc05e6 1 0 \\x000000010000000000800003c912fb8fc6fa4b04dcd0c3ca4804acfe14334bce57da571a8ef43781e7f59f85770332efaa00659a154dc9dc115ac7d43c1486b43c0d9944de9c0f88f7c05b6e3cde1074659f3af657f5952a0392565f41c123285de8bfbc4d098a2db2c39bf1041e024aa9479fa33a395575e274855f99bdef4b017f22ba7dfdc9902df38aa5010001 \\x7307ce031e6f27cb70c4db204e00fbc1429164cf45fe0e46bb3bfb362158f0a666c903a2bd784fe3571ac5485dac9a4200b41ed94a26fa16b16254ed2c42180b 1640170407000000 1640775207000000 1703847207000000 1798455207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-98 \\xc43cf7cc55cacf73bfa6f6217433d9675133501d6020765318603aa2fa224f6f9d2974d554d80888fe47235eae346def8e74f70d27d1b8ed926155aea0055833 1 0 \\x000000010000000000800003c0f3a11e2afbb20796eaaa3aa49f8249a3987650b2dba8055ca5d996ec5c5c73444ab4db895d2a8912236f716cfa36befb4b3c441afa1c9754d44d8971109fcd38aa2ff267628afb47fcc595f6e683a19013a8e95c6b5a7a2af29d665045f5aa19a53886c4a5b1531532356c35016abd4d26fcd9e687f24e5a58baa386881009010001 \\x9d02b95a2a92eabbcd10c677c08e12cc9dfd1457167a95aef5e6ec473ff8ec4d14b89e042909febe29fd870506ac0357373db977cf220ea4288c34d2eaa6040b 1654678407000000 1655283207000000 1718355207000000 1812963207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-99 \\xc4b873d4a42efedeee6285730702a7f4fb31c726c0d5d3236f56726792f8ed8c79a2bcb4813ac8ccb756991c8659348d185cb8a2243394e5456b5ef850d2eb49 1 0 \\x000000010000000000800003a7e5065b18211144298026cba9ac18b0258cce85551b3251b3006734fd25944b9d212bc9ec68540e76ad9833097d0bf3cf11a5b7e9d6709fdc4245f8252f119bd5d8bfff1240d187a92ce8bffe85d74ad3a197067c61b0f296d7a1c20c01eb956f0ead9df5df05f030d02d74b342788eb42a95295d475a92d15f40c0f594e51d010001 \\x0e0371cdcdd168db937acd59dba9ea01d1b361b61277f011ab231eeab4390051b7c07957738106b9c60c86be0b7f1595f0387822b7a7961591772da074ed0e07 1652260407000000 1652865207000000 1715937207000000 1810545207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-100 \\xc65cf8c8a380404a862256275c551194bf83880d18f1853a8da3bf4fafaf3f9bd96a8461f68ace3eb3a5d8ebfced939a04178cd7bd5a9671b0be9336404e9f93 1 0 \\x000000010000000000800003fc2a8bc577e2ecd5cf23f2e178cf2f9063b110af7267e3f3ee6f30670a0978c1604b769c25f00cd01aa7aa3e09a70e11cbd76fa128ebf9fe38b2e1d3ba4656ca99b20314f2d9f5d6963d6094d73a42e4c4d6f111b3f794875bf8da53dcc962b2ef6d85ad90dc58500580c91cf11a6e13081977ebe9cb4e0a43c7be269c51d9f3010001 \\xfee5727b12ce933c0762ed13690458f51da36dade8bd4a34725aaeba06d0d486ae55262f7b3a63884202286031953d3e6601cea1ca48bf7ebc50294e2be5b103 1667977407000000 1668582207000000 1731654207000000 1826262207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-101 \\xc7fc18da6374d15818bb118ace502bdb38e7fb6a0a2e1dfd236ac7dead9446b348bf72f2c89ef0f450cc7751ace4e7529c6bee0b427ef345a9f27f64a7bee7dd 1 0 \\x000000010000000000800003fcc446e83f4e5873968347b8ac876c4b6091abe98e2e137a64c669ba4cccf7445a1e08f7f920795d3071e1d41447ea171e58e40b36975ed562be8bb594c5bce68f9e1cd5ca70fb47d31521355ebb0d6e7dfe0a933a4145cb5040f4bcee06ed3cfc9cb1dadcbb4635683c3baed66d5bef4007d0e4e9467dc1b7bb17ebacc7b139010001 \\x549e8d8183b416d54cb0c30abb9f774430166ecbb7ebc763750e6166945222ae0cad7ed63bb3bd9b64c06fcb188483f954a118c1f93808c9f097b4e16c7a9e0c 1641379407000000 1641984207000000 1705056207000000 1799664207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-102 \\xca18119b3cd9b2a5e5dac49c958a4c5b75934e36d07be37483fe8a488aae1cc0486d7a7db9ccebd9c67b77993fc7c2f1519dc3ec32001c79dbb9153cc8698742 1 0 \\x000000010000000000800003a0a7b8015f2190904187f5369350954b283485fbdda82cf9558a6703099cb4d3e612d9b34e05bd34ba792d2c05f8cb94a04a7401445c86461feb4c49915e6ee2be6c849efd195917d7e9745453a726ea7d6efbabaf14f504adeede782e470148527848cc067b83558172a83054bfdd6100fbb09cf5d236e59d9e09c85a1255eb010001 \\x87b8ab6f973783d9472d9fc3aa8b0c1b51b79e22c28c797473d3a706048872470bfc9075681b1d9f8b4009d174d62111ade14288cb9221b95069a9e7be4ee503 1640170407000000 1640775207000000 1703847207000000 1798455207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-103 \\xcae82e83d002aee720ae611be90c65f4609493fc11446a8b07d416e281616f54997e8f915a8fd4a099ddd5898a5e37b325efa657d5359b8b40ed2a19f6bbfc67 1 0 \\x000000010000000000800003a4ae9aaa65a1b9b2b0739451e40ddada1e642bb2145f2e315c493347027ad60f4e023835641be7c0b635821ebfc28c2f72a5623d17367352f725f0feb202c927587336d95453ab0adf9170b652131353d53af521c7eec252558c77233870843c94e046ac4d977e0ec60bcd38abf03faac396150bad5e0d1d5d0640a62714c357010001 \\x2d975150651fd69861352deaf7536bc57d37db8d18e1c47ea2fe5458e2790ddcbac6b99f90a20cfdf1db92b19840ad8c80fc5e5e35756de08bca7e474c1a940e 1655282907000000 1655887707000000 1718959707000000 1813567707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-104 \\xcaa0bba1eed96cea618332b401a7ed2c3f5ab3b43c2837837877de7e4d0b1e9c7fed63005a46e37e093c3bb07f667117fe4fee923517de53ad029061fbeb4e26 1 0 \\x000000010000000000800003f3f490c0df747428993eacad00af066f233fb9ae501a374c40deaa6e19cd019413822ffcf06254dda1191652ea403aa694ad1544aee863edf6406063a8f6e2b951d46d2dff09d5d957a4adc79e78e721f95301ffe310b08b042f73730adabbf46d0e3562aad6398043315de4bd4d4b772d7ee2b45227f8b3c7cbb95f3a11c1ad010001 \\xf8b92251b571162e743164d8fccb57dc53ec6f3fca6bf8af2ecd906f6b22084dbf6166d8e8d040a6ba280f16ddf598c3bbc48e1825317e88c6323c377d14ba0f 1643192907000000 1643797707000000 1706869707000000 1801477707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-105 \\xca604ad4aa1a48f5a4cb020ae1c41989ddfe6cb61649cc42843b8530c80c3bd8a4510e89b2242d3c1ba960e2e17ba1a73324646ae80ce46ab9d544e03f9e7bab 1 0 \\x000000010000000000800003b8eec2b600bd80bf7c2c22deb08b47cc938002b951085f2df287f9f4531a51a3055ea03d8aaa192414ffb88e3f37e9af86139f929b5bff38dd10632439d7b4ef7fab869f9f2d57068767b304b03e165003560fd4da31f18a4b50bd893701b05a521bf499670b802bb7a553739e9d83586d9b0cf01fb48b8eb44be5f1a749f00f010001 \\x0c9f327b98bd9449ab10225cb129e4dacbc0af9d57fa66c1a35a3a918b522b01a90d97d594c1bd8ec8efd62d0242a2dcc87580bce970f9d60fc8ff4035b41e09 1648028907000000 1648633707000000 1711705707000000 1806313707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-106 \\xcbccf6edc8499d511fd54ad2104c1f085b702bea91a861923e6f11eb222ca51362aeb507a78f846822d337e2656e57cb5cbd3e9c04c8e1d668ee2219708bf6a3 1 0 \\x000000010000000000800003c3ad9e8e2ce76161a1cc2ca2fb55392a812c6bca178a020bf78366afc49f5f952db6615f66dfc164df1a326befd1271838a98dfc60f5898eac5da9492875f7b1192aa1b9608ed6249484538863e4979fc0f3a00da0862c2e2934f3351967cb330a22ba35a79014bf3d58e117d356dc22ed49c10b07a42206aa218576ad616bbd010001 \\x511a2c9c4addb76045aa830d48333b56d72c45184d0da672c53dcf0262ffe8652cf0bc90a4fcdb194491b69b3194f029c6bb94f87a7a2cadc15feecc2a80380a 1641983907000000 1642588707000000 1705660707000000 1800268707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-107 \\xd0003b0d9669af132ca63c56ae001ffc39bef4b4e0e5a792ecb52c5755bab1fc1efc0c00e55f77ceb287264936424b0d31407eed820cd5c9a1ba58e66094c680 1 0 \\x000000010000000000800003dd560046096fb8b1f8178b599cf633f94e0ac0b62e361f5dee502543697269a1558ef2e3b3972615462363492382671ae8edcccc7f114f74172d07bdd677e8b93865fdb6a8dbcc5f08422bfb4bdce80d2e73b055c80c749d64e0cdd92d473054c25455000d82d1c1fe40cce550c296dd7ff190f3c95f639987afea300c83a2db010001 \\x81c57fefb9634c1f53a7a9a2005581e2d09edf2d0732f0fc5ca5c596fcdff4455f2b04bcf077aa7f44a47052750de9a985e6ab559d4640cdaf6ac30e4e2a5104 1644401907000000 1645006707000000 1708078707000000 1802686707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-108 \\xd1e8e85e50939a454f1b60dd887335fd5d7e6ba1e3be660ab23a3067ffb0c34a4286608f44cd47c7095f32f83ca72e1d6c4eb8ecd98bc53cc95df17c01244902 1 0 \\x000000010000000000800003d82fd1dc03455d0cca6bc1879163f8deca051ed4cf0ef00a2c7f6179a58d11f9f323f1ceede3fd26b2dca04bcdbb77ac19fc3c6bfd4b61150dfd8c0e26ba2604134efbbde600e3af9ef1d69e5cf70d549e286225d2862df357cfa2cb34def0c6cb77154fe1b6632ecdcc2cc89829096856a8049a648a7fed61227672e2e4dd9f010001 \\x50e7e20a94a008ca49ba58c78e6381658503f34ca09da6d9706ec45be031fda15dedcf827e17cb598ace30e6123df0ffdb1d50465eafafa6bb859639ae72e108 1640774907000000 1641379707000000 1704451707000000 1799059707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-109 \\xd46c1eb2b15beb565ef19cd754a70874eea025e2747f310f62668a8dc077860c7e93558fd736beb700b85a8aebb40e5d39904f48e90f1b3ef77874f0de19ae69 1 0 \\x000000010000000000800003be277c85c858eeff4d4f06abad250441dd1b25291dddc6b4e1a900943717c7555ceab4e1328791d16b50d467b41aee5cb53c202452b225684447676e2c6dcd8aa7dc31542e4881fad4bc808b2f1ecf776e7513e9d1c95373460492d8c4ab90a72b1d1258f9a82c9b67366b26b7ad19e1dae335cfdd69fd0906b21624374d2e11010001 \\x438b27a767ca8480f3bcfd5b11d4a3df23634ecd36fc05d918bc6918b250a077ba6588c57ed66f6dd6f890cc830b1b4e91ddcdc055a762094a1f1394d6086109 1664350407000000 1664955207000000 1728027207000000 1822635207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-110 \\xd7984fa508f3392c86b8f9e4b8c5d66a5800d75efc71e5b24c1b71513fc8b1b8685344c0f1e6e3e590daceba66f9fde02d0fc1cffaafbeeefbb323a62c04e976 1 0 \\x000000010000000000800003be66549d10accddeaec4f0e020b3340a68391b888e7df3045750b2ec3f4da0826b69de1c45445013445711342ab7ab39a29a4bac4b51330bf8f6826005c045ecb74249eb173f0638acfc621594e4287dd88773fb6a7591492f7aa06b37f4ee6eccaf79bee154bc745941bcec80d76131110ae5f60a682691592b21f37fb90053010001 \\x6db9a2030e6b66615e3b64f3600a6e3503eda9e5c6f901e8348560dae5bdea66ef0ee305b168cd3447eda103d6937048c159152ec63aee96c2ac5208dd5d6604 1666163907000000 1666768707000000 1729840707000000 1824448707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-111 \\xd9a01c1063bafc8e6234b14302eaa3efd2f98f0c3a9a5d2f89161a5f7689117f4e7bcd37ceb0070763e333ee6b0b17f9b3dce5a904c0e662c8575a259a532303 1 0 \\x000000010000000000800003c2c8b245290b37328dd1397710fe444f007e0eb445dfc1425c3864cc896cb1cce1fd5fecef35e99374aeb4dbf406a4464a2e8e0db3f2970de1bca3940be5f5beb7aaeaaec5f864215bac78a419232d5471dedce2bea401ba0e684c6bff2d8fc9bf8ee06b3b3f52f003b6f9eabc4e0282451cdb421880feaee3279f3223bd94ad010001 \\x889b1182cb8f35ed1f8c96b972e66c20924f73295b28f00d0d96e56083c8fd64837abdc520c99f42613e48698b590d7eed457d6310ac4dca2d40c3d5ec90960d 1663141407000000 1663746207000000 1726818207000000 1821426207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-112 \\xe1a4cd966e8e48ae37270e841bbd6663d171f099b59db4ecade266105a911cd194b6d597238e026f553b5cdbe372cb963f75aa4633874b09ef52b2d6875bc20c 1 0 \\x000000010000000000800003c9223a11871ea01cb6cf573404a7d5b2bf34c5ded09a6dea67a2819a31f4981a8383b97de72ccd7064ff1c714f9bac7a0ff010cf76a144c0345b057f1e276444d11af827e6873c74fea2e5e463c79a382af29548bc59bba5c4d8a1ddb868d5def058d5849eff40560eac09a147ad50027464042e11d606d4b304bbd1f2ab1647010001 \\xe745b9ecf66812eea8bd21a9eaa913890ee73ad31e1c7c2f264f68b21a3b765e8a31fee330512ae1a1f0b70d977c9a42dc560e84c5453b53b952679d63e4c006 1649237907000000 1649842707000000 1712914707000000 1807522707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-113 \\xe44c044ee97d376c8bfa55a296d8e9c72b6301459ee8782e57573c09916900e448877a4d9d4bb019485aabff67804259fe79e68a287fa8f086028f9a4a24e997 1 0 \\x000000010000000000800003a938fe9eaea59ccd0fdb326a28a4620198ff7dd0874cc6978fcc8f512bcb875440344370f26fe9fd2a0f4aa1a1853c0ba8cd55033bbde5e06108f410663f765d3ec4e0848c6e3a2357b1167d3085fffdc3c109368ea5fd42517b12d7a5294a1b683dec9dbc2afaabc9244b913d3523d61e69968039a0ec042b8df2f6ce47fc47010001 \\xdfc05858379ec496600ecc44b76fb0386f25f0ef11988807bf8be98a5e38704531ac06e9474debb07d22fa6c5e18e59601939088a901a4fd894c6ee5bcf51f08 1657700907000000 1658305707000000 1721377707000000 1815985707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-114 \\xe61856c28ed94e93bcd8bff0f4b82de95a8a0af85b09a24c728988d8441d8491c43f8a45103af3f3c95796cdb2ebe719a16f15053e52e9e8be896146d8eb7a06 1 0 \\x000000010000000000800003b55e71fd1b61012ca6fe26f9846be1232d261ff827a23e98aa14c4a26abedc80318290dbf5cc326a15aadd4e4124e95d848521d375b61edf7649087abd4091a6ad988af2a88aae98973aced2900680927a5229abf3609b9d5532d66247603db8dbabc0c3b5c72b4bd0a63f718a598ca1a169dd1c1ab78c6a2f50d38d3c7274a3010001 \\xa6ce3b8a80c26ecfafe1734dc18218d392de526d25779661a15643dfeafb7d324446677baf2b102deee25b022554f37774ec800f8be60b2cd2e61ef58f20ef09 1653469407000000 1654074207000000 1717146207000000 1811754207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-115 \\xe6a023e5713985af543d4bfa3bdf54121607837db7d1138ef1c4da3036d6a3df75a6b95d5b9c48a893ecfda922b70f3c59304abed0bdbc792b1fcb8b758da83b 1 0 \\x000000010000000000800003f655b424d8d5b785d925fe12420393983a23798964aa66a19dc9f80cd345a6c2caa9fc7a07f1b668a1012e6ee23fb7aa377ab9c595a2eedaf4152d0bb188c247342aa3df2199bc7cc1f158a1fb439e7c3a6a159afc170306001f7c222cfb806d0c0f8c454ced41def65296cffea83fafa37d90687c2ace4d767a0faed07492bb010001 \\xea9c994259d69c9de69a858f0cb94fd9d48c19237a88677ec7ea33bc623286e0fb660c753246eb2c70d6077818a7a3a0ca9bad4ad1352b4d3beaf19df4807a0c 1647424407000000 1648029207000000 1711101207000000 1805709207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-116 \\xeadc70edf58b1f4b7b08cd98d7975dd9f879d05ed63444f311a2ab8d0bcaa3e6a3846e2d807c36e87d077d95d0d2f59e228f0f6b927d39532a1ed4ddd33f0c76 1 0 \\x000000010000000000800003b4ebcc0af11a01a273a3756c4a9cb50fb4bfb4d36227db829c09c859a8312d3114e454e52b154da61ef6cc6799cea434e11eb5332aa12943fcbe109ba6e2d2269651b30e45ce533a5dd94492eef2a5e259210e8a1d86b773ad811a05ceffbd7b6791ec55d5d341572232733c4511cb44ebbec44db505a43780829e1d224720df010001 \\x42e07a2bc9f2618f1372ee6016ea9d5c5fb946fe00f5d27e5b172fd5c77ca07d871903425e3061c23dc6a6456ea913cb7c92a1207e4c8bc97e166bef2676510d 1638356907000000 1638961707000000 1702033707000000 1796641707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-117 \\xf0705811549d6325009c9745ee9fe372cfd5dc54308f06adedfd07f3a98514d49eb0a4d84bbb91982a000693f067c7d466daf9d019a23f5a327f4cce908df809 1 0 \\x000000010000000000800003c340b534e52e4d660d31b7947e325419ad7eab50b415f76b1b977cf6cf8d84732bf25c5644813392432bedd2b507964f1f5174d3ae2fe87409e43f3bfe7bdbe1d14445831c34d1de490810793cf668ce6b621c2178164fc0ce7c3c08433b0d27ea31f5c88841af7c025d2d998308b96230044db1603c1c2bc28051beeb0da8d7010001 \\x709690c1b5e359fa48e4931971bc7dfe2136e8a40c2415b004af6c059d515efb4609b8df7f0b8e2ec987ac91ccbedfbd97d3e5cf34d7ce118f69744eda82ec0e 1645610907000000 1646215707000000 1709287707000000 1803895707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-118 \\xf168c275773f86999fff5a5d23892af1bf50e673a53637c8358aa1629f928a4575751381e70a4b22d0661abb2864ad05629ff0e1656c2226121cb265e0204249 1 0 \\x000000010000000000800003bdae2e591559fdf47fcea130f71dba6b421e16658e865e3975ec0f560f2a4f686a1fbe96b01e896f6648504fef4ad51ad9a43b6e3f798ffd61a4976a8b1faf7224eeca269c52139a357ecf950cae3acf4a071d0bfe4abe794c4a9fdf476326d7911cd909377d60a38698fd9f8c327299162976bd47a9dd7b7411dad345c21e41010001 \\x72565d2de1ae91fcfcc504d094093d3fa1948718a6243106dae261d00a41446610306a12768601f1cf2ed916e3d1b673191cae05b8f3bf2308d818bf7e3a3106 1643192907000000 1643797707000000 1706869707000000 1801477707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-119 \\xf548d688fea8bca2df101ebc9d305b07ae4ece3d68c80a3e5902211c18a8cac74efb4b09ecafe2575025648db64dbab872434659a4c0e0db6028ca83c0c461b7 1 0 \\x000000010000000000800003e23e349d85a97c9d572c7b9450661df9e42d9fe81a4dbb71b268a7a41f6be40e71d82854fca9a7575ea7d3b004f73eac77676df4e8b075a1edfca134a29655d1b66daebe6b8654f53af262d34cdc63b3359db17a9d22361211d31e6b100ee57ad6a70b107f22b87461e7a37414b299c76ca309519c5f4688b1ee46e4c1b3f80d010001 \\xb4e3e3e4daedd99fac5735461a3ad7bb4732b140cff0a8d7808cb581132d4910e9990ae80ee3521c7df0c1a3bf162ea8f14947968956d08563d25aa22b255f0b 1663141407000000 1663746207000000 1726818207000000 1821426207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-120 \\xf9e480430f2ce6703d905b4b4234ab40c7b71f25b16554e1ddf5b3a157acc448c0f9b6b2b0cc603a320ab09b887310fb2495f4ca1ba86ac439844ee51336929f 1 0 \\x000000010000000000800003df4cc746d486c5bef3887624e6bec2c0499f39e7b64bf7c0c5e69dc66e3820742ea062a01a2bce1bd1ab81f55e025e5a21d126ef8f9f7fd2d452b7441e1e5c385693a3db59762b554e908ff465bc1be275fefc2ce9d9d5c4d198414bda9072151733a27cfad464396a37ecc21b3da5b7df0bfe6836e9a569ab3b763d9427843f010001 \\xbd3390c8eea521757bb121c4520682631ed23e9d01ba1689c3e414baf2c9b6025d3cb469d5061482bd58c717f254d9cf9d0a88f626f18f0df81d1301a08b2b0c 1658305407000000 1658910207000000 1721982207000000 1816590207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-121 \\xffbc3f0ac66e680c561a40ad6637f76e8f7ab756ddf47fd194dbf9832789bfe3cf4324037cb3ce09fb716fa842d7cc5fb4db3df0929c34d41884db53807487db 1 0 \\x000000010000000000800003c16fcaaab8ff799095c5e81f002bae76e41184615e6735d2f92f9c6fabe94afa56377505d62a76c152a1f01d90a1fd6ff9f6b99a62b62bd9622189bfe30196bd82ac5f9c0bc450452d539be15b63615a07c3fc0b96231e3308db3ecfe2fb2249363cb2b8aeded5eb2b6e2f317ff04806f2ca8e37230c19c1a398705d2e591763010001 \\xc3a7daa14613cfdbcd04c78c5a43ef2825f0b12711dbff59f3e99df551e1d4ff9ee64e6f1fd972e3942bd285170efc4351616b88f567e5a530e8d306d3f27c0e 1660118907000000 1660723707000000 1723795707000000 1818403707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-122 \\x02d9512fec81b7bb0d108f151fd98ff9ddb3ff3276dc6263088957f64efcf653c5122faeeb20931bc543f911e9655d03020e529246bdae4029b35d4629b6a3e9 1 0 \\x000000010000000000800003bc071db7681be3c16871b42896aa2b90ae61baed7dc32d6752c426b4eabe8d46a90b3af508d8837ffe2fbfccc7de87a7458f3e92d6b3876809be579e8d02d5ca2b1c8137b413fb0190134f959dabbd3d674cf9222f2c638c7648a9bd0053e46e00e682276825911cec0aadcf285142fc8d858907663ccfbeca69d563656b778d010001 \\x2b17aafe244676ff26f183885a7c584cad824fde01f42a8e08097f0e3064e0d1cf9defe29f7c9980c7ff854b870a4f0dbd09384fbd41b85a1927b328f6c5a606 1642588407000000 1643193207000000 1706265207000000 1800873207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-123 \\x03d978d97fc860af7727d4f1e1bbbdd885834fe7fe5323e9d8b557afe03aeec767beb8af28ea2548c68e3afa2a1980eb91ac2f4a27cec4bbf05212d50073e96d 1 0 \\x000000010000000000800003b29f1ad37fda3e260384954ee3b63fdb55d2a433b6e5e1b53bed449cba9927f377aa6677e70142e8caeec6313084b53b6ec163897c0363f25304249ddc54b17ca598c616c4a2ec9c0467c67d9871afbc2e6d9dc3cbfa949dc517d71de42a20cb6366fcb1766e6294c0c504bfe548b29820df218384b2141ebe0300f88f60796f010001 \\x45c9945afe16ab384152a992bc61a3c1d8f6ebe5ce4c08b6b4e59687f99c5aaf8fcd9db64c6c4e189b2086ee32e16a5ecdff1ac13159ea34cdf5f838a4bd7408 1644401907000000 1645006707000000 1708078707000000 1802686707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-124 \\x0405dc2d0708cf80b12d9262ab3afee47159ba8e30cd6929a45bb990f8f5bbe73c2179e0e2c7b759a1af8dba2d8eb3be43918ecc4cfdedfb3f65d0e010409504 1 0 \\x000000010000000000800003a67c14de480e6e1fbfedc1d6a289973708d0cc642d28e4e41adcf0ded4ae87e5395d1bc254fcc97cb34269f79cdbbf24a7a0bb68e80b7acd914ea6604ccdbe68f479c21260f4bb0df9c52f99cfc7db436597462096e1d60a0262189a3b54035948b0829c7ea5db7d4a35e888f5d36c21487efe39b8840eea75097eadd4c76c25010001 \\x3c371fc02bd67c079c610995267b5da580d4f693ed86ed09ac0b1a6668ab0deeb3d97023a20fb976c86b9e1cda1dcc6862c495e838f9c6036c796d4f671f1202 1658909907000000 1659514707000000 1722586707000000 1817194707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-125 \\x0619665d441a506b108928b1a054612dc1f792b9bd221b9b02a3eceb7f23511e219ea50903dcb7138c604056d4b44ca6dd2243004d3c32079ac9209dc5aec028 1 0 \\x000000010000000000800003c1924a68b3c754e6f55c34b561d6f8056b2a53e020d73082556b01592ddaea4cad06e331bee22c3de913816c2a5bc767f90be775c2b88489ec1e23473b4bbbca70053724a488f052db2fa55dc04c8b456c902670160196f79b3a7bd84bc838e4a54c5efef967300d1f7d8697b0bbd91be0b8d3701da4134621436ef01249fd6b010001 \\x2adf2785b9a7b349d5cf2da4e50dd63d875d218c2980c16fdb14d93c3136028bdd7da80ffa0f92440dc158b38219f14283e703d874e1668dd3904ce619bc9c0c 1638961407000000 1639566207000000 1702638207000000 1797246207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-126 \\x0d415ef4c3b0b26416acedc2a5117f2eec5071e3f1104bf5e50ac3fe0be7a83195db0f46084c98342d4c3f8f54bfb88de57352d79a8600eca83e3a7be35af4e2 1 0 \\x000000010000000000800003bfbf399584f107234751071c9099e18ad182ac3f16a222278f7051a0440d502ebc96aab84d9b2d4d2ff37c8e78cc2ae793845c8020fe0d6633b1076da5dfbf6ad749a1a9b20d595faef6923172a84cbc0ca1e01bf7f64ffc0f6b2004933268d19da6c8eadfb3039c3f1ab986a9b303b5755d4b0e5ddd6cad4aca5ad9823d639f010001 \\xee8fcbb00634adf241cc3a2868cd6eca8869ebeadee57e6af24316cba59a71d869972a269f0680de405f0d0278c7bebd72d14fd08bad788c9421128098a3890d 1663745907000000 1664350707000000 1727422707000000 1822030707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-127 \\x0e45d91b9094f60271dc77677883f27132202b8fd18716bc4f77246622017418bc1503dab1de33a7fa2d66b43b5acd7c23b8e6d4b2fd4cf718c676147b65c6c4 1 0 \\x000000010000000000800003e06a438965c025edfdc637215fbd2916df94bd248f7289a63ddc89e1085e33876754ad111abd200eee57ea95554f5544fa1911de1f4af60c11f8de91234b15d3ec146322b1d0c307d6eade9feaa4104848ea22350035c1fb456d90c393663b7bb0151546ae85e83230c945688bcaea60e08b04510cf85d42a76b57ffb143e221010001 \\xe4c0b7c4a4c1079a2043b7a8cc9fbb0e48f532f0990b0f0e690c3bf4a80ea70375cfc8dd032cd2f77a6ae81f78ee141d03deeb90602da62486f6ac70ee5ba003 1663745907000000 1664350707000000 1727422707000000 1822030707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-128 \\x0f39427354adbe8d2fdddb4af42db250b666ed321b3c69bca9e0eb26a3adf1cf6bbe45cc0789d2583137d5cf88b7f46b197b0f89702fd21399edc1508d7f7873 1 0 \\x000000010000000000800003e0489fef040f6356762bea9bc9f245ae3e82c3e6447fee37c921df4789ae89ef12a549727a2ac46b7f6c86f11be89544be03fb546b2cde2d773e6e8af89b8d6658f9cf40dec51d59b0988e94e818a56609782719f041bd67e97acc629d09d699cd54a88718c00d40ab89aec4f94b8e8bd5f1a5f2e5bb56d54ac1a2514b6f969f010001 \\x53bb398ce379c8dc1612862a037e5f66dc60bd8c9a3a489e8d79ef8d68b7d58f73691bd45fa70b28b7d728bb7189cf79d6ebec342854b09a5a183a918ff37c04 1638961407000000 1639566207000000 1702638207000000 1797246207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-129 \\x14415f738567d4759dcbb9765624cd744516e3a82115d44113db9698712a0dd3ebd6a02fc688ef698158cd487451712324a0c6965b310fa05b16d186c11b0eea 1 0 \\x000000010000000000800003ea15f6416dff513922f6d18faceaa66f2f529804419b4f945026330a72616292e7fd2f9aefff885e2e21fcf0b9061d05118ebe1c73513a219713fdd824662b0cc219e30700af3f9c3f1302a892e79362d0a3f9f5a88a72ba205df0d254a0b162d4ee20ca9d10bcdf8f1429c396c8e0eb1bb918983fb38249c45e21eea36022cf010001 \\x820f39c04638c220b20a1207a46cd9aa478b3922f35009280ce4616973de8eaea0229426cd88a209cdcfc4ce1557efccd076b126fe2a0a63a6e3a982a5435a07 1659514407000000 1660119207000000 1723191207000000 1817799207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-130 \\x1a61bfe7bdb02a0293fe86e6432d005367c4d8c6dcff837faf8c883e7535a22bbd501ff00f94e2bd1d785b1972b6faff67ddd8f2f52fc5c9389299b1d1aca30a 1 0 \\x000000010000000000800003af03ec407d75aaa5c96e2ad933a36a8e8464172df8cd5c6830bd22659df24de94b0c62e9c65e593b63d4cdd069c13c416e8f744061d751aa0fc5961aaeb6ab11c1312c4f1f2ff1a73fe8e873e118a00dbf753278c2a7f153af4de0deda4620fc906a42a640bafa26b69f4d80fbd415040f8dd6c41adc9b901a5399cd7ac2f981010001 \\x1264f8a1789ecee34d4adc565e383f24f16a9f702f43546a5b5e42f95a8faf71cbb3d9ed5d51b8c90392654c5ff6c9ce8bcd1c05fa9b5d75da7496f761d9890a 1663141407000000 1663746207000000 1726818207000000 1821426207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-131 \\x1c19bc3ec30196c755b0b783166deddeb076650a39bea9b7012b2bf333eac993cd7b27ca0d3b33d9dd64bff35c660324127983a65e0ff770bf66a923dbbc774a 1 0 \\x000000010000000000800003a99874984f6d2a4d961de107e21f2e87880c581aaca76fb954bd861babfa34abb098de91532d5bb4e66682ea5312902ffd0362cce2e65ebda71b7de902126119b14590bc9b480980df1c385fd0ad014fff40cb0f7d037e84d242a175204618345d54d020d376e87824b6ea8d3296b5990fd1d3b890528d59ea22b2c093462b99010001 \\xa68914f0f708d3466be4fa8de26c7544d3b98a8ea05b4940a1cfdfce22496568bab86a17260d3c0a934919abe129726bf1826d10527adb798d31113e465f3900 1666768407000000 1667373207000000 1730445207000000 1825053207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-132 \\x1ecd193bbd6f272a328e9fb633870b1190469c0a526308ee34e08765d66f2a6814695073db37bd71556e4433e7673dd2d92c1f4fc798c78a52bbc6d52ffb3eae 1 0 \\x000000010000000000800003bfb61ce1504086cb8602f09ed30726f50cc818ff62919a5f8238cbc342e8669578f18fbd52e707fa0116135766640f298bad209db5a4003734be68bfe932274a60d0cee76b03fdac131d12941fac3f2907bdb2d3118f96a905da4b30d5691b6d740218e9d5703854a721abce31fcba447efa87b025e220872b1a0f7d83cd1929010001 \\x5e06e975e049e649a92a963e35b8947d784e36ff633704813618c3fc1e2ec0db60cf775db3664b596830f99fccf82d38866f7d71e56cd04e0d13a35fa2a22906 1649842407000000 1650447207000000 1713519207000000 1808127207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-133 \\x1e85a80703801ede10b3db8159ab6fbe589b6aa85d88340bda44a8ed78cd1d44088bdbff51f33079e32b3f2bfd6e08df4b70a5c43b6538bd2946832487744b2b 1 0 \\x000000010000000000800003ed3581a9b050552800d1b1eb1e1a22308b890dcde2956d46b3349e6c1b0228a504fbcae811a6c93bf1d51c5998264848a09bf90375373a1940de1c9c184cc816e19a698d82c480037afb1e6aef0734d24240943eb4946668ba4adb29fcd784233e17c1d36b1cdcd14b02c4bed74f42c506abdce1aa61b6e43aee76512a8718bf010001 \\x0f0d2a273340c7a0aeb572b81eb0970e08436bc860f68cb985735e586314d6a6304931150191f96349bf546a21f95762101502745aedaf4019e55d086de3c803 1664954907000000 1665559707000000 1728631707000000 1823239707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-134 \\x200d360ce14c043039c867d013d3c7a635f8e3fdd1154823156147a00949dd8067efaeab8c640a233e04bdc3e34ee67da16bd86d849faae6d72bfd2128822aa6 1 0 \\x000000010000000000800003aa3558995ed48a7aa6eb2783e30c142f32922e5bccf8d922eb413e94f0fd324333c424a880e5ba8a51fd28e57ef3dc8025594df71167f4b095906d142b54c6ac39007e44935789af6c68c29f101e8b12b9a708b5bb7ceb6ac2f34ac53ba3598c4bf3c1d880bdfec3d4dd2b251861a3c473e8e5a6e59b758fb2261b230b17d513010001 \\xeb4bfa0aa732d504aa10d23a11cbd515f13b6546fc31cd14b263c68cc8043ba8b263e7fc595468fbd1d64f739c14cb951b74f054a88e7a4217656aed6c05ed02 1646215407000000 1646820207000000 1709892207000000 1804500207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-135 \\x219da360804fd1bb2c3dd5b1f60e89489cec06b0058aa8011a97eca8da01dfc77219286d39ceb330501442b5c7551936835d1ef164d17c9993731f1f8c46db17 1 0 \\x000000010000000000800003c14eb6cb966212ac9cc3ac1d085b36a521190de27f67b54c565ee37d919c299baa87df24947f0ca2ad43eb986a47e5da3fd9639e73079bc234c4ae6dfd325d7b74fbd9dec74f200c70eaea91d486de572c010e152d587e0831b3741c90769ae280bb9d888e78bb3c83dea88939585c4f598c6ca657b78e0c47ebe6dd55b25083010001 \\xb82da326d3d50d8d7157d94016968744cfd6b20e5224e8f6ec28773aebbc8fdc0875ad0ecabd879cbf16fa8aa2adef7a1e9ba276c602c111c1a9ed4f64d1ac02 1667372907000000 1667977707000000 1731049707000000 1825657707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-136 \\x22654a329b94c15c36e145a22f2c79482331f047e1a9dcc7da5219d1dde505d53f2676c0079be85599eb68ca4edb888b740f85e5636a21f7a2daab37d3f712b3 1 0 \\x000000010000000000800003d92272ce2a68f4cf499f7603223ffdb4d9ce31f8f826200320863e50c3b95d48a49d0fdeb4409f6525efa8498db736524db370e1891c27f4fc5721d955ed74a315946795bcd975b30a77faf31eea187d22d90e23022785ee6187da12ea7cb67ea77f2cf2067edf224ad47642a81556a79775d006bd32d721276c301c31a6f287010001 \\x662b047298fa4e886cde31508c1487aab11e7fb58bcfefaf9f75c227a0daf453dfd5feacee613094aa96027af11d6b9086cf574451550d251fb7345d33a3a601 1655282907000000 1655887707000000 1718959707000000 1813567707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-137 \\x223de7651ffbc593024c412cd41687230b076e65261303270b0fb415b37636d59f5277511d1373b30d16c9e3db6ed9fd5e47c65ac947b804eec902aa90eef005 1 0 \\x0000000100000000008000039dffc9045d3c0af1b10c3385a8b55b031c4634211e2803b1a11ab51eb0145bb56a543565cb334c4706be9903852f5ddc144cf5f95bd1e41d96986ad4cdae70e4df623a7b859f6ea926659bec8345f99d530aa4b88b80ad32c15e0ab0a64342bbfff336a812dffdfe16302207aca8c670406bb0f9c24bc49c182e3ded56d477bd010001 \\xbdcce7ad7e16439a46d0eea6e456bba0cd8a8509a30746be7932e7ee257645056d695966c0d885119e86487c2038d1fa683cdbce6467581972b659f5b1717a02 1640774907000000 1641379707000000 1704451707000000 1799059707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-138 \\x2281463600a2be40da4c1f20225dc54a3246941ce26a94394f28eba8c65b77c4843d06db4675ebe087b4edef81664c86394105d2a3c0b4aa040e2812513e2d0a 1 0 \\x000000010000000000800003c9a5fd76f4352caa8ac5d0698ac2fb14f2e87d19c9ec3af6e2cea251180ba1dc4b8f743d20e843615a2656e0aa2b292aff6cc71adbe08608a231bfc577fe00a5d03e69b869a709a82f1129fb53487384a1121e752c7394edee4dc5899092a023e5ef14dd42bbc5c3ecbeac71587dfc62a38a93215c06dda22542902ce2b1dd5f010001 \\x897f5d7ce3f62b01f114ab78898a268eae4fe5aa915d3fe12dd81f77586cf02e14fe9d59cdcc4686240422675f1a625df67dc773bd8385a0e231113a7d5ca80b 1666768407000000 1667373207000000 1730445207000000 1825053207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-139 \\x26796ec6c42987fcefffcadf2d82c2e5617c39063d9bf03760e39f38de8613501dc24ba2871aa2943153c930079615f645634f96ed2e58c58a4f9cb915c89d3c 1 0 \\x000000010000000000800003d7b7eb8418618c6862c749c0a5e1697359cdc29da6868403809bd41f281781ee9f5c6f1894c932088789ab79199856d2c5e0535bcc84a0240fb678be5a543d921ddca37e541a2c631973d60e5289ca3e241c8137afdd7061f7d3a4b4348a54a3f01616b2c709cfeba3e7463c017aec680d6e55ff21b47c5896f8c20aa387f2e3010001 \\x8fff2f5a26698681445280a03a64292a2b197317b66072d52f1c24dbe1c681158386d7cd6fa7957156b549efbf9720c9162ebedf31162328bf9c6545d1652304 1639565907000000 1640170707000000 1703242707000000 1797850707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-140 \\x28a5cf15b0dbd07ed999731ce6c45e2caeabaac57d0e98f489c289a089b0792cda4aa95d74d0d48e0a51c625671efe2e589f79a0d503e506af870afb652b6988 1 0 \\x000000010000000000800003ac7d9e9144feaa347da5084c48f0aea712c994de7057a53217f573ce5160edbc16a225c0f1d4c847f9b7964dadceace4c7eda733dc17dffc944a1f48ba8bb99594c1bf26f30130703e81a1f6417d8a01e4f4d4e9be4363e4ca17bd4694d28bacabf57517e2d143573c15449c40d5ccc566f1fab09cccb7304d0acc2620da76a3010001 \\x8c91ab04561f245fc8ac10dfe174c84aec07fde72a67e4758a7df9ba1f88ab94fde053732524dbc847ccc71b5bae07d8d0edf26c9eeabcdfb722a96f7458fe0e 1661327907000000 1661932707000000 1725004707000000 1819612707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-141 \\x2ad1fcad9164b8187c2c80e4718503e2da367d1baca43886cf7270ff431afee2ef942cc7cdb9ccbe751be02b97b3c339026fdef00e73a1d4c7a4f26f07fe84c8 1 0 \\x000000010000000000800003b89e2514cd90eb04292fecb4c62b662aa6ce2d593ebfb0c7a5eab771cc1ebb7a5f57f3079fd0f4a28d3cc86eda4487f3d806aa7c1f8d757f9fc0d3ceb2ced76f19f7ba2cf9f3278dc59f6fe864df43f466a0705be5b3ce68c8608d2b296c100510e8dd3b8a595d05b579a059744605b8119e2cb2201418e12cb513d065e31b5b010001 \\xb484c74aca2f717caa5ebb8eb8e049ecc8f262b16bbda5e2bcdc631e5706beaf2e1d3a6381771384319a36348718d492090413573927aaf381769c79806b4707 1659514407000000 1660119207000000 1723191207000000 1817799207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-142 \\x2df55749f82c6c3babe4ea950439be13436d788da36beac3a790679d4c344dd5d660e4f439fd0b637e71a16f344eb24c96ad51080af330deca310b6a5909d99e 1 0 \\x000000010000000000800003c17dd3f71096b68e6ad72181b2a13dec350b53f8f888d5d32af1da4d2a8504a0fd579c7837ff9be585e1cd547d969fc2142c8ec89272b517dc2594e2c6e54bed0da1b917b4c6464afcfe490f898de177bcf0eb18c866f6b1361ca51b4ca2a2723bd865f5950287c97451f8ea4c3b9e56a852493fe1b5119bf31ddf1ca18c6403010001 \\xb69a0cefad9c044f9c3079125ee26aebf8f3b88f0d9b5f537644040a74562bdc125bc4dd910b86ce62d6cfb2f323f2b62281ccd8309779451b74083644b4c102 1663745907000000 1664350707000000 1727422707000000 1822030707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-143 \\x35c50ebaa84a5f5efaf79d1087449c8ecfe64029d3ef59cc612db7f623958062824293f9c5e4123f925b69bfbb825012a0fc8b1a5677a1bcee97673782928950 1 0 \\x000000010000000000800003f0d8caba37052691c1618a7e1a9f0b81d139925d8c2c96abcff30c02be154af9fa2e83777fd06d2ab346a5a2a2c4b274cde546c564ee5a8cf564858f4f6d9cf87386bf14eda190a25f00867e308a82d48b6ef89144fb2966419d9a6995957280d5ffe37bacc3048e0580b5c5ac2528f681fa40dfc8f84c2be2ff3a0846479a99010001 \\x7fd7827aa9ad91b91d81e1a5076f3a6334dd21c57512c16fba7b5881716f7f5b26f85c924afec43ba0f4cc3ee8e3edecf60466736485949c3fdd30f0541cb505 1642588407000000 1643193207000000 1706265207000000 1800873207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-144 \\x3b71724482c2d496cbb897313d28020a959936bad8d70ca727881f197eaa0b03983d602cec09a8a8a1fe9dfd3a3dceaa09770c24cfff42c6575193457c31bc7b 1 0 \\x000000010000000000800003e9da4a2e0b1ff2d0e379a94dc39fa56510a3f0709a1924f6a7983d7001e2772ee39b1d60345fce7f71a34166c31131c152be117f5b14b00c4cccae2634c1accfe9bedc38fce71e85b8d1d5506afc3b86046ceed22403cace09cdfa154aa9dd98daae3ae3aa3b4fdb45c8c835f1b21eb742380747828b5ba76d25447c35fc8a2f010001 \\x0e4652b40a057be76cb123f4cb428375d11c39d8e9540c37cd532edccf65c06c84c664945d59b9d2067f10f81de66502aee3a2d34c26e59bfdf6e94fc349b701 1669186407000000 1669791207000000 1732863207000000 1827471207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-145 \\x3b19d2d1428347fd05e35c58ae3c3d4e0a7b2392ce38ef5c74ff8ae21947acbcc0ebbc7b1a72644fad6aa01b1fb89899b73d4beeeb8fd84ef419317d018ee23e 1 0 \\x000000010000000000800003a41fcaafa604b58a264d3cf2ed565d2ca56414bb29b624605ef8238d62a6a86373b51c6d9903f273ceb429a8409d47f43e34fad5975774ce7d2f837a38fb61090ab8e2052e0e75cec29131052cc5b5732b91f69e7a68ba216ca2ed4ec65b8a4c9c81914e66d43abec1ee58e6036fd3055c02b05d78274c7d422ee295e24f0c4f010001 \\x43957db5692d0497109c672cc1e2bca1a4f946f12a4a284ef13680cc3230619bbc07ddc48c83bfc08467cb31024329c5162876f7a15f77eb2cc8f19314935c00 1646819907000000 1647424707000000 1710496707000000 1805104707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-146 \\x3b0538514309afe7bcccb988cecfe1c20de1d80819fca633f4efd3e576f9a82f5c462263e2714c515857513710cc3ce10f0e15e475f8336039e2de19a7c92dd8 1 0 \\x000000010000000000800003db5710dc3151a4e70ea67e49193e56fb9ac723f99827f6be4f9079c96a5f8d68831cda5e1043b9abc92f97ec0fc7da904212d8c13e678cb3d4e580f6253ec5a47ad4988572eb1230b0bb2cd59e01d4cd43a9d75ef10b79cd1d152dbfaa22b5d6ba17d9cdc33a8f65d819cc38fc727d52998136b80bff6d9d9a06bc8594f7bcbd010001 \\x9f3715af2f3f60018e05b6e1bb3e9c5e9bb9b6e3d0a1f325e82cbd5f302ba1a3a93fead90f1bb4631885cde69dc802ebbdf2a844bb8e52ae29da73e3c72e9106 1669790907000000 1670395707000000 1733467707000000 1828075707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-147 \\x3c153d98df912ecc17335f02b6575ee9f142657b0fefadc6728c4cc44757d44922cdd2667241d0cf0f1ef569bccaff90426312a76840e56b3f3186d60d49ef5a 1 0 \\x000000010000000000800003ad77e0d4e733607704a6fb4385a5dd5a5cd9b40ae98f461ee476d1388b6c22bb204b77ca7aa91bcc94e8869d064072ca9b9d844ff4064fd147077a8a3e8eeb172ccb6c9a6f2ad4c9e035481acaa93eb62d7eab5499a5540aded04719de55ed9e1908517295394d5d9e8815ce3837fc2323b4545409548f118ea5c039abea6c3d010001 \\xddb35904559a095c7763d310ca49294c4586deadb00c63903e1c6b59ccdd35cf70212675f59a8e64442d41b06d1c32dab7de989cedf9f7bb3cfb33c484b1d00b 1669790907000000 1670395707000000 1733467707000000 1828075707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-148 \\x44fd941afa07c18df42f7989043111430ccc9ffa3826295b27269c4fe481f6f6cd5471956504fde2b2053f3a8b7b05ac2dcfb64191a4d7f89ebd42cbcba36800 1 0 \\x000000010000000000800003ebb0eff4cec291c93b6db5c710166525302d7a8b8392ab2c7c308888963c472085efec3fcca13f823a4c41c215cc76c3da7deaa79454b4568bdee58f052dfd004d10cf9915a90e282dd13396411d4ae6dd9cb0a4f4d037184c303419d266758a36854d8181fd3a5bf3ffa951162c3ff1da82c85066502ba80fc44818831bf4e7010001 \\xb9ef40cc8db5c8388d64a5f5d878ef4511037aed9d06c055de27f230de8d8bc3cf9d4f9f691c7aa42d1f09e28de97625fa70a32e99138000352c8cd238fdb50b 1647424407000000 1648029207000000 1711101207000000 1805709207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-149 \\x44812904b811f3a5e58148c07ca5b6d752e68b913ea803e42c5cc774e5ed1ecb88e19f1a3b7c8c37034f0bef7d5220034325836f6291803c8bb2d5f7355f8042 1 0 \\x000000010000000000800003c0f0b14e8f71607c8920d01553014f2d5e3660a1e94a4e550dd6c29b7840161c11d25857cb3ec355432698552864ef6cbf6e36bbde8923d0da057f478a62aecaf244ff795c23105dbb7b3790af9d32821644820f6f2464380ed358ada2fa58d17dc4b5c71178678fac9c04d5d9661349d7ed955b1050d1779fdfd6a62e6bccab010001 \\x1f4860762174caca2427325554de1ad6fe76a52928396a05280543db3c2460b31947bd35a50dd849a0389e16bbb166e6d6892da8af45a9ac2647b28fd15e6c0e 1652864907000000 1653469707000000 1716541707000000 1811149707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-150 \\x4ac14919e01b54f014aff517ec18f98c8d19f1ffa7a7305c8a7dc65fe0dfe13162fb6af7cc0c59a5eb53f737686e32adc5741fbef64825e02822f1577d57d41e 1 0 \\x000000010000000000800003a657b550da3a10fbae5dd729f5e4fc974dc5548cfafaa16982a26e5a2b6b6bba350e59e3096beb47e0bc69b1ff9174a5cebec9bfe38348ce4f3ed65bf70278514cbd244c65a92bd34fff2d6eb81969a7e23729421f657c2abeeb831bed23f99897e950e3477c92711d2fc03f38b6007b0fe44afcbae6fdea8871c9479fee815f010001 \\xae3b46d99dc3a629eac69651323839c4bd8e687271e0cf3440c8f47735c757116ff582a7ca42bc644f0ee6b7db0ed7620bbca07d81ac5b959bcc9205f4257605 1651051407000000 1651656207000000 1714728207000000 1809336207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-151 \\x4c1d539c7d10b99534846377a2f8185ca1ba74a924e522dc7e14c422abf6bfa7ca5ef28958cbcb9fb4029b46a6fd5f40372a7ba6ae756485b2a99d6911052a99 1 0 \\x000000010000000000800003ba1b32321db443b4632afdeb725c22e9ad686cd361f1c1b664981a4412ae0db787488867b8acb921c72eed12d2b2a84fa10fb3f6b460cbe672fcd3038abd8d4400b3acd4b11b8d08e563253693be68cde2e06c8c1df70d8c964afc4d324e805fed719db5eb99e478539fe084ed0deaa1ab7953de1d0e629b7a9bd387a2f33dbf010001 \\x7b32f7328b261ebdaa66040e07fe2f2979f63ee662a9686a2e958c3c0c912bbd23ea3c7c1da97121e51382f7c43c80e63c4fa4cc9f9029fffe34350ba3f54304 1657700907000000 1658305707000000 1721377707000000 1815985707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-152 \\x537975678597678314da43fc3dfbe27cdfbc2642e8361a150d35a0c3d5b80883e3520a8295c51c6a9f997ca58957164e38e43a1c7d6ba850ab5609b0856e00d7 1 0 \\x000000010000000000800003d216eacd09b957220d6af41d1c4be5285cad3dc3c64c632bff9704027d430a95726b3655fadc0e9a83ccc2bf32e0006712938ba0c37c76344b8dce1534347a76f38adff15e8e9a7be143cd5ac4abe1209faa19f0e29ae3990647373742a04621ef4b425e0cd300eec3339f3019fa31b82cacba95ac93bb62f0ae3f9d4dc1f89d010001 \\xb9f940460a3ce2936a03c322f4a5045592fbf3de97988f72d581e1c4865d0037854fcd9c89f0f84047dc8f8383d6d797014a6463bf1f0f88ef43e1874c9be20f 1654678407000000 1655283207000000 1718355207000000 1812963207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-153 \\x56858ce35bfdbf6f7be5a299ee079dc75f4e50d684b0c979d2d3912b1bcb92d8f7039196f30d64741769d0e3e8a04c698218412d0093c18b2e60b376e35c598b 1 0 \\x000000010000000000800003e3879ebe3387a2a66c480b2efb26c3185fa488cc6880aa3748b1ac071309ab897de16ba14ac7ef3b1a34ebc7431bb2b45cc8b30a14ba744175e6a090d9bf0176cc2ad8ed504504dbe4c7001a123e4ad0697be436827ff8ed9ec52f6c1799907247ae5be23fc4bc41b5c745b81ddd754fd4adbb7a3ff38bcb5321b6beb159e993010001 \\xa01f5d84c67573ebc1570a0b551e0657b61cd45e928bc02e4352c875ead571d04d5ca0638e4efecc3ec7211721f86b92bec94f36e920dd48f62b427a2afe090a 1668581907000000 1669186707000000 1732258707000000 1826866707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-154 \\x5755ef2eeb0340832e5a47adbeafe519c3ab59420207a710249a2c800ac8f2d5fa64221237c960b43001b614d7e0152225970a29f729602473e99a8840f1a40d 1 0 \\x000000010000000000800003bcf1fef5ed05fd1ad804066ac45e276b32d859ff28fced9b24cac6aa82917dbf1948108740fcc5106f63b59a27ddbc6ff92c7b8e1bd96f754c216c1a3255fc46c378eb2004ae76f7955bf800eafef2c9c5d483a03d80fc1eef21105e08911b363771bb303b7d83960b17f6490ba3cf48c44ffbfa9aff430015ed3647affaba6f010001 \\xe3c6665c78f7e5abbb9dc1050d85a9bb684989ff4df855766924c536ec6802cb76c95d69bf201ad8e2d4998a484a7134f9701071edb43f5c186e8bbfaf00be0f 1662536907000000 1663141707000000 1726213707000000 1820821707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-155 \\x588931b9d0d2c7eeb96e9e597d0da5642a9b300e2d32cce8d6cdaf6f835f6f4460772e67c214fb85ae658d79d4ded7dc5a3a6f8b94772b926f6db6db903b7160 1 0 \\x000000010000000000800003cb16fad8b255abf924afac96190e9320f1a8af3c9f72bdbc77f6cccbe593b90b5324ac70551ce0d98d20d81e3a6e5f3b3141476bfb213dd305eb43e394f5ffe95d3085fa69e94aac63c113ca01181e1138a001574f9eadf4dedc0afe15613ab5c493a6a99ee92ff09df681b0939445d506e48e34747adb58f75789e27a5d7247010001 \\x7fdeade2c24e8ec82ce37d519339decc2679c35e312679fc64066b3d1c1eaeaca6695c4dd60f141127838f978824c5f58cf2e64715d17e88b038a93e5116440f 1638356907000000 1638961707000000 1702033707000000 1796641707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-156 \\x623d228eebb27aaf940fcce68ef85081ccd0697c8e3ab3f0e367957ad9a00fb80b39e27894e3acf496772e278cfc6f114b2163f2ec843062c69d9b20d1d4d285 1 0 \\x000000010000000000800003c690f8b9629b8ea065e9aeacca17233cdf7a327b6960ae7141c196af68db87df87589373a93b971a7e0b304c6d0409e9f06b94ae88628b1ff198d96a448c6ab1d8652eb19388e11043619ee3621a77fc6ab5f0426ca50dd49e19f45d6b35cf0cc9dba41c52d7abbb5464c8e18da68c8017f7ee4c04f5a40dc24acc1afeddf2d1010001 \\xe9540fe34e30aa38232fdd504a02057ee9535b590fd937cf3d98c57d6dd059d133bd23e17ea62c34cc88eda675d0e005e15f4012a99b33a64334fb706116af07 1651051407000000 1651656207000000 1714728207000000 1809336207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-157 \\x6309a1bd9a269a819a9531343d1be703e84b9ad2ab9c7840f1bb77616ddb502026b88ce44722ae1782463a3e1246c3a91d4074ed32fda54ff4cc2a850bd777b8 1 0 \\x000000010000000000800003cc92c4f4f8bf70cdc9e6f237a48af057f6d4b597a07e340a1c4beb2ca6a42672b492e15fc3724b1a6d65337253a6db7d2c690384aebd6542b8d0d3505e97815f11afc0c3c42096013cafee596058e05649270aba53f7697eac0e9cb8367bc65f569bd56159ef64dd30606ed5b432bbd13ef5e1cb5ad1f7a0d53c104acdb3205b010001 \\x6a935a9ec43660023349dd4985cc39a4dfdf4114531ae3f2ef1d88aca316b8600dec8a54379ff468789b7b4e20cb70f412a90aac96bbbc6d59e06a97863cce0c 1649237907000000 1649842707000000 1712914707000000 1807522707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-158 \\x63c58c60a12fa1c505e176ef6e5caf55f66ac2bb1323682ce57b5a050c9605b9f6b38a8e1efcfff5c35e2d3651cd930d2aaa016c51592a47e6c7fcc296e4e102 1 0 \\x000000010000000000800003c5408cdb91dafba446646d1e9c535c62449d92aace1bb0f16b1ed2ee17a97d68716d172e075fab13f912d95108bff287e704a6490deb79b766536c9c45dab40219dca7853bc1597610ef5508e221d383a07af025dae469e94923da72f104ef524629d472bef46d3604453d7b7a156265464868830d303d45373d007fdf650bb1010001 \\xacbd18d2f99aa485a97e811cf7f210cc749132398b6c88ad7f49d994d703a3a4bf346c902a26672a53145be43382a2dc8d530f4a6235a4b4d27528921c2f2e04 1638356907000000 1638961707000000 1702033707000000 1796641707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-159 \\x63a56d745ae4f2a8741729d7d88e7ba71ebfd096827b4a855fbb7632e5d0da8be78b19bb297bba225fe08692aa4470dc1d66e7cfd01f72c4e982bf93b452d454 1 0 \\x000000010000000000800003a73c31de8a0cafb15ccf2531af5be4af67ff64dfc0022aaf1b39522e6429dbad4383b1029c1231bdc07d8b69f7bdaa01dc4a8ceed531aa18ad64d1abcd236d0a31aebe9b9eacc0ec50861f7d55097be84f22318b20a6abd5340757f8ecb4d8e9fce9dd625cd4fe682cbf741af8b88af2a568b1045bb7438684e03b82e44f7bd1010001 \\x4737aa50d13d5a15e6a7a2451c344443336b45a6c845a871f1f050942e1b1f312a845e4173d1070fdfc0cb3a1ed5decab1ab496bb929b46486bdf30cf81a7c04 1666163907000000 1666768707000000 1729840707000000 1824448707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-160 \\x6b49467955395d31cff84faf97a31157067b9ced45855d9a31c6741b025e65b60b126457bc18c08ac0681ccf0269e9265ced4131c7ce8807fd182c451ba8673a 1 0 \\x000000010000000000800003bb10ede28124101ec08c09d1a20092608b8142bbec7b046362918e76ff97182ec9275cec0c9819b7e7edca59465396b3a2a71c08a4d44aff84720ad5b5789b532b9e74e8be86e7d1a731f23cf602a073c5ca30672e53dfa5971f4f85c1db1dd65b0164cc60dba4734fd66452a4a1e39cc40035699d94c9ddf79c6f57ee2c064d010001 \\x168db54f481d831aaa096d7d04e47d2fb2270d91fcc557845e5b61323a04741e3a2c686d2a0a40b1a676973f9bf9a772eb1fc0cd5cf2fa895a76060584b25300 1639565907000000 1640170707000000 1703242707000000 1797850707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-161 \\x70118c39152088cad055ff26947fa187daf522b244d776b990c70395aa38633f192dd86c18071d0746fadabed4783f15ee854f60f37b89de579c85a4f1be650e 1 0 \\x000000010000000000800003c17e8f321c627b2d35c5c138f486865aeea075741e0d5d8c218bb4dce7c84716c2de15c79e8d4d5e9152db41a94fabefcff0e1aca52f4bee859cc07d8b49445c639cfaf25dbb97b61fba6f70a3c28de7b1c498d13a9b5f34b76f361845809917538a188ea7965d2e55a0e1b460d3d125310bb0f45bc8e9102c048a84ad80d4f1010001 \\x932c75d56d5772d41bd0e93efbe1b8588f94854c15b4154d7cc4c5708c413ba145d2aaa1531654bab56f088e03baac710d66607c5bf24fcc3f483c9eb184b708 1647424407000000 1648029207000000 1711101207000000 1805709207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-162 \\x70955d8e35de31943b19b675fa22ea8c0f262584795ffcd7e08856dbe7f4960c7944dd48337c5190cbb1f1681387ed76a3a28cf8e1ebf701b49b8956e17ccb2b 1 0 \\x000000010000000000800003cb15df4d27c9501339d05f63acbd9449fbf87139942ad26dcfa93415a82b41e72a138be180558b75915d20bb628604f7c3d04ba259d2a1eb2b724f17bff09570de9392455e8e63138a22edc611b57dace00a039f5305e4367787af31faabdc7cb3bf603690555bfa6c338c543753c4d750b29065fbd0ccce709030f3d842605d010001 \\x1b4ae8ba9c26c206cba1d42eda2a645903ca140b3429fc819b5c22b22d4c6dbe5792372c2e65b80ea83e3da9ff2ead1f999cca872ba33968f99d32989be3ca03 1647424407000000 1648029207000000 1711101207000000 1805709207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-163 \\x722571c9cbd8dc3502514ace085450fa2da2b7129962f64989eb39a42e92845996afdfb9d32c76f2a1c635bae2fdd4c6089338a543b807da8de921fbb9c93877 1 0 \\x000000010000000000800003d715e14d5041b3da077b26b6aa3db33a6897dd44ad5c13d6d1894f0b26fc1553aca40ddc3c268cbb390ee6a2d6c38d95e13c9412b2694f47fbee2a71c7cc15e558836a4e93d899af7625566c428a0eb074bcfea07ebb19120df3f185534cca55e7fb56467f0acb853b2bb8265bb89020cb160145fcc5f57e15373adfd579f6f1010001 \\x08be0c1caea01eb881df9c91e58e33fd409310d0a73b00e4af01635cd674c68abd122cb21d4f33de06d089b4d4713edb33b6a99ae1f7aeb567605a39159ed409 1654073907000000 1654678707000000 1717750707000000 1812358707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-164 \\x7339addc26f212ddce9a3d4df9468e6364965c445b53438f9aa42dcaf3f7b48928b012aeaec7d4d81ebf48730d06cd0465faa6b01919b5606fdb03650cb02ff8 1 0 \\x000000010000000000800003c5f92023eddcf9c4504f8e1ebd13b0f268415ca4a483b1e3978699fc9fa2ccb833692a2e6216cf53a922316342eca35526480691318cb66c96f19d087974a370351994753dfbfd322cf10511c18805893de7dc8b16b838fe4bfa242240ca4d31235375d21581f42a4537dd260327b947b76c00ba9782ad786f51e09a4ecd5827010001 \\xefb0c00a1ae75f2f12b80ad008b07dd1c6bb870541f7af2883efaaa6a8b92a5baa90fdcbd723cb2b48b28c087cbedab8e1ed95982cc833daba3c5f14b4a10e00 1667372907000000 1667977707000000 1731049707000000 1825657707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-165 \\x75492fcb5acfbc10ca2f997b9ed8f196515cc27aec4270dc97f372bd6acc53e8575863be0874f9f9901b90e4dd99a05c250c112bd2b779ff1fb8293bac2f7a9f 1 0 \\x000000010000000000800003d8ee166a4b8769de3fe1277729f24ef75208951ea1164e9dee5af94f67c053dc0007b505232388f19c838ebe068a8e66f8b3812833b97fc8eb71c79b2d0bc76e2e5722c38c2c8053bc3d728e3052f425e543e1e3737e42fc0d5174d251fc4087db3fa32cbf8ef3b83b96449bdb5d4cabde32f283174cd5c947eb8610689166e1010001 \\x7c45a1d1b30c7442c8432f14a09f0cd95c2a5cc0b4e2f847f0e5852a2362e4b4748a96ebfaba9c7c25dd1525a1fdbd65944300f8eb18a288abc1024553078d06 1650446907000000 1651051707000000 1714123707000000 1808731707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-166 \\x770d9aa768bb1da2421e8b1bf2aa3591f3a7aa312b97db7dad4e4daefd71ed7a5c4d9a882617596743de02a79890e5f4d7889d627f07ee667655665562af0a49 1 0 \\x000000010000000000800003c3e4e3d0e820a8ccd44fac95ba8bb4745ed947e6049ea78f9620362ddfede77b4843f4bff12a4c74d7af518cdf82296db585ddfb342e69208d0a7ee8a0d2a8ad7c69aafa444a3f610c7129f23dae75bab5ed4573dad71569ba248aeac4e27806626daa324fbb206c42b890718116997e7050ea82040201318287d328d089e443010001 \\x8d49f906bc900cda51e417dc75b111f173a06718d02fea68367b9e2395afdc808cabd0293010a7a697b2aa113fa3d8afc9764e1aba8c051887475f09d2220a0a 1664954907000000 1665559707000000 1728631707000000 1823239707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-167 \\x7701de84114b0ddb781293d93bee7d7d329f6ed2cc8efb0e02458a811604b7f9b039185765613fdfb7d4bdf759c1afd6ae41b12cba1f3d9e3550c6b2696ecc23 1 0 \\x000000010000000000800003b78e3c17eb31d21158f28f2912174b8ee50a708fb4ed9741aa33ae8ebe2a787858cffa1db937f7a255c18a3f1db8f6410dfb5b137ecae00bfb33ead3ef691a17eaf1b67ed60780ce3be567ca7d475071a94087fdf8fb527cd12b021587af7057692c3fff76281bb459c50d2787dfa726b4da02e4af8a37a89b96d2b30975ad27010001 \\x0db5d90c3c39645c39fdcb8e65b7fef91e7f9b29fc3aa5e10512b2657bc602cd1aa7233acb35d5bb46d46444b389e76899513438701d66c1d5913fc77e6c9a0c 1640774907000000 1641379707000000 1704451707000000 1799059707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-168 \\x777136e6dfc8636ef4c597226540b50e19a61fbe86dce64d516c09588e9ec291b75939eef02cfc13810bfceb3818b5b829f289bda18008f8924132b310ca3d20 1 0 \\x000000010000000000800003ca7ac963b29fa6766d5a3a3163b51cda4c87d216ea2d5eee6108d5519a470246e8ac9a2ed3384f7894cb18cf001dd282b1addf6bcd6b910bf3a031a4dad3bb813b8b11f3e9504b0c89e63d47441e7ffe4b7d6047db23234834cd2a794205afd901a82e19c487c72c68cce6f49417a34e7cda46bec3b595c516b4e01a795bbf9d010001 \\xb569a74fb512309f5a034f5e4f40299bc484d0575397560ebfbe2085ce66752a8404d2740e245efdbde2d194b4ab2a71e625a43010a569054160e5c0b6626807 1640170407000000 1640775207000000 1703847207000000 1798455207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-169 \\x780dd53e4c43ef8e74f4e6e62009e3588538f72774e8a45bf484315af12816275769659ca62341d1ddb687fc71c8e77973947a1d5f1774aa1ca6840e0f754ff8 1 0 \\x000000010000000000800003bcd405b746343e4515ead0d7265ba5f5b68fffe618555ed668a175da33190b0a4fd5106a946ebf33ac19c8892e0f8bd2502f1989c77b4d995241cf6604cef45ec10970961cebd5bed9d822becb4b0fc68593c424e23f67c89103ddcc347609f409a34b82417f581b62dcfac9f1709f6d979a24c6fda9b070b165f3c7732b4209010001 \\x0477edba16b630180a3b57c292319d6da3ce0407cd41a409d81c2012eddc4e3a19471d998bf6bd55cea935b330da3eccebd27631d257f7e281f8065c21b9b20b 1669186407000000 1669791207000000 1732863207000000 1827471207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-170 \\x785d64eef1faa396b2525097432e98ec6f38f838fc7ce7f4e8152bf3b43ef0c41cb12cd797427c0fef8b1e9513fd86b8a8535fa26e262c2801823c286d9c5c02 1 0 \\x000000010000000000800003cbea7f6269a2b64434ed74d9d9604b58a637a1c403dbbfef1f0d015c6472f5bf4f102bc189557a02ffa99995687e0e8b5a04efced17abd4d00fe2f9f7527f10da079df4361cf0387ac4c09753796ab4ead27397f65a438816d912abf308fb8399d0faf4356cf468973a2e60bb593e4414fb80c1986583f96b2e090e0d82c0851010001 \\xdd4aa74e7a35f888df5ef46e53440c78d61fb31b3265be7db635e386bb52628928adb7baff4069b1cee2fd4e7aacd5e70acf3e3b8fb40494448bf2426a069f04 1664954907000000 1665559707000000 1728631707000000 1823239707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-171 \\x7949be9e088e1caec150c831cdee65cea98e316c23f8407063a86496c1c8579064798c98a23ab86e3cb83d05da5f8f07d89e88d3ce747c12b066ea0fff2a10b2 1 0 \\x000000010000000000800003b4a54ef9019dd8027b51620a786b256a7fc3b57b17ac026ab1eaf75c347cf42c6b7dd1ba17d1043ac9d1000ba866f8ece8068d2d456e930ad6dd0ea7267635e23b98513111614f64fee22c897473020c5e09d62bcbec22cb72719ab640782dd517ad45d0adebabb04f896548f972c97e477fe9aef328ecd74d6ecb7070e005f7010001 \\xab21068c3f8b2a322575a0d044b61bf8526723dfded50663cd3423305a25107400d50bd31d61b6da6b4a277f293d8523b5bb7e084fbbc9e61957c3d6f1afa401 1642588407000000 1643193207000000 1706265207000000 1800873207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-172 \\x7bd913fcfb61f685347f44859392a30772971149abf1ddf0b14fd84c0fe59485acac0b83199fa89b938d4239d649f37015277d1d04db222f582b28341c27d5da 1 0 \\x000000010000000000800003c68b4483cc0cbb9f299af1a381aa994ded584db0ea24f97c963767384dccdfd03a2756f58cf40341f5c4d7c22e8f567e7e4b4ee5bfd719f47ba6db6efa354689297cdf3dee010135af157cb7cc9547051a79a6cd6fba6096196d0724e0ba9540b2859906253d45c866ca31e178478a8bddec3fbb4fa54de1b8e775e24b8f3fc9010001 \\x94e3cbcc79a6e7d05a26854f97d05651edc5a87b2047889c0907cf65d9dbe01539976ba86760c26f2e0396e8d01c4f97793c148547272a31455299866f306a05 1650446907000000 1651051707000000 1714123707000000 1808731707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-173 \\x7f4d7e6ad484ef0ebac7b8a34b58a020d143bfcc2114f1c67c1631f86cc48e7bbe81872e0b0d5536a1ac5605eea5256d4b9c42b186f2a96308060a1a8c576144 1 0 \\x000000010000000000800003d5eb011c8c2b3f883a57cfcf9a15807b4dcd96d89293833a985fb51d3689c790663e0c1391dbd4462ab3a09e8359aa5e2a1311dd3edfdc8fe5492d7e5ff710e2cbf96dc6c6e500ecd42b7d0c778f6fc0bec354e6d39c5de7ac0d9a8ebecf1d04a9b5759150ff33cea36b4694d2dd7fef65a862f71fd348a290e13b82b2f47293010001 \\xbb1a96e6aeef7883fe4a7edcc73e5cf90a1d6ff9cae296c130c5d4438c5b1f8d644422f6f3831cdb44341aa4cfefa7f0e2ab72db4a306494711583d8fc1d0d06 1654073907000000 1654678707000000 1717750707000000 1812358707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-174 \\x7f75dba5d780d2e4fed7f56693b8344b5ad885b9a7d8ed4b27af6c9e666926e5bba876f928d55e10d03e306caf6bc148bf4a23c3682b3aeae3357886efa74a81 1 0 \\x000000010000000000800003c98b1eacb09209c4aa1256cdcb6ee416ed0423c35ec1673bdc98016d5ffce1cecfaed920e679624001fce32c6a574d0ad158c3ce15b3192ce41029c83564527f1c1fe05775b516d60819b0ffd7943a092eb0db1fe80666398c271c4135cc98d1ceb4e58da44b340cefcea3d4a7754e198c4aa4c458d6b0109648841ba0d8d5e1010001 \\xcc01eba84a6dbdce0dbc457f28d2b7cd0657cdc48f82c09da625b1856fd5f38329ac394ef30341a6aeffb5484130d7f8718380b6aec316adc063fab9e407c50f 1664954907000000 1665559707000000 1728631707000000 1823239707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-175 \\x7fc9bf5940074bdf7a52544868f909fa71bc0956414d5737f556a03c933453c6a7721d3aa0f29a3d8cd95e39670522fa3179d1c282f67bb920a94ec3d98c8b2e 1 0 \\x000000010000000000800003b08c3b4b175fd51a991c927722187d5fad0d362ab1653d5ab1de7cdc67abada9f033707f60fd21ee9faa093101aed75678044246ac4a935dcbaa66f9733269af53983b3de4bdf4c04f97d3f556fe7ebb0c6218696fb2644c16f559344899f0ac6b4a2115daffa29822c481b6003657472b0c08ba3dda2913a7fd0cb8c5e1f19f010001 \\x855986ee3b06375677259f9f6c6974c9b335b2ec94f929de2a2a771f0c02b019652670754c98169fb047fc7866ea956c77f502dc8a46c1baed3e7b966c79210e 1641983907000000 1642588707000000 1705660707000000 1800268707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-176 \\x83ad663dd2d4d8eac1936b456a96daff27bce3d30174b392a9afc1bf41dc79c9175aa5f330bb9719e64247f87ae938b00c5aca649555c06be3efa5115b790bce 1 0 \\x000000010000000000800003bda8d919008545b3bfa90f09693178ffaec7ad1976cb8aeb3446b231b8926caf5b2ef4341cb70bd257694c4c8938cbb9785f0ffa3eee071172339c1058ecf4bab21f74315c6392764176c7799a472b875ab3048dc4f9afe1bcd3ec3cfd52e6529e0e199076ceffe31b4bfb0e45972569951fdc35e6108a170939cd0bcaaaa7d7010001 \\xd54c226b036287a5872f470a29f54ecc99d1a222ef7cb27e081f45824567676304502d5102f92f366881ce1159c5f3c4849611c7e230f35a5b61cba994be3206 1660723407000000 1661328207000000 1724400207000000 1819008207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-177 \\x86e1795db0e3af41bc2c37e6fc03e7acc52a65d39fd7d4860654832133898dfd0de50bfc576b4adf815e36b4c0093042c7948a31610befed91583cd6d19e37c4 1 0 \\x000000010000000000800003c8fba1c15353d4686cc54649d31e04d429d20b69161c5c13e1cdd057a12170e181af31b4879f6ae9e3dd5eb4ceee1b7c1334f54a1bc8accb07d1c8d09938e0fd069722078f2cdab8d51b93378b491f10a800b07a6500b5403b7c23f975b469f038106cf3a4e2420b60e1384a0f46649ceb45f87585cc4ef65bb320250ee03ca7010001 \\xbd2d6d5d5a4d245061d693a08028079c194084e5c21fd2e58fa638cc94dea962c8b233779ab7220a52d3ae106d9522d78cfd1bfb23ad489bbf7f8b13a3b06f07 1651655907000000 1652260707000000 1715332707000000 1809940707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-178 \\x8649c33cfc3d0a47f086ffd0cf7cb5afffe0e77a82c0c12cf24b0b5ba6f181aa38628501cb9b54823d8d7adef2c5bb21abc467a462cd113c04f58cc19178fe90 1 0 \\x000000010000000000800003bc1c78d1be04f6c39438d229529890263631c8e786998a41473443854d633dc4a2b84829351019902ee5799195c6aa2da71a214339a7f27d2a65b409e25a1530c71bf34825f3bc7363cd166c023a5872929407ea83bd4713f38931caa22fe4aee3c953aaa9245f2b5170f4706a4ad8cc323693af22303338cfd53bac6aab07e5010001 \\xdb5dc152d102bf1071a301d7ecdba68ce7142a7a2c6e5367fea735b24f8010fcdfa8b28d691efcfb81e031ee2b8414ac0f15e88c1de3b4e00898a70710164e09 1663141407000000 1663746207000000 1726818207000000 1821426207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-179 \\x87bd9a29d5f416973725824f7ef209856bb17ff658c71efcc1edcd08455b2f6900139e13a8acf506944a405df42e719d707b2ffaf9199aa888055b6cc144c519 1 0 \\x000000010000000000800003c053a49a2429a5abf1e462856e2632e7a1124c49a82dc0074e60edca25ed18d5c7497fb12c54deaf2b0fc6e28474f066e3c4b8b927293025091f200a0d5039f988cd2216693d75a24a941e5cb464b22d7042a47c5e3c3a09addc8ee363e53f905be497465fb5097fd10e2b3552166d984bc8018518cda17c748cd537caaa8319010001 \\x44b76c2c0e6a2187c2b845948e87532c3a627c9f8fb8e45770b9b634e7aec38d7277962b8c5f9c19d00c37dcae11072f16e719c5b83ac5e7054deb7bb605c200 1641379407000000 1641984207000000 1705056207000000 1799664207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-180 \\x884da637a3db48c4113230ef65b075d2d6e567dc1e2ab342686807e96efa122bff68f2b3547632197a121cfc0e98af407ffd9eaccf0ed82dc227245d489ea273 1 0 \\x000000010000000000800003c29b0d39de7314f9ed366f188e0857fc8d8d97212904bafc65447685189ca760dc9a7861ab114f45ec76d5d4cfc1c750797007d6e2428d6c2c57c5f98e003718bbc1990a6108b68f3b52d3b93227775eae616dd91751f51a8c1d5ffa3643032e81e0f18fb56678abf9dd99b365f1702067d192cc0ed7cfdd54646a1c776fe8d9010001 \\xde9ca5267a51949b95d19e369a125eb6353b5996e90d3353502eac8f4501f303312d7bb0788e703dbb80ad1caee9b866b6d8b355586d9d549110fd6b4dcd1f0e 1639565907000000 1640170707000000 1703242707000000 1797850707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-181 \\x8cb1fc2e1b3c6dda066a4d7be334a40508d2bd37b913e6fc134d2491abeb85a9f5f0e5a9012e9627af79b3a04d878aee14a35be29df80a781460ac625b6b2543 1 0 \\x000000010000000000800003e06dede90d259f9ca753fc963a67042fd3ac7009047af80d883884ba1a8223e12c36dd4e4ddae06575f9830a2bf7ba3ca56544fd26c7d8725fc3e2e597204ea416631d3175a0f6256a3a759487a7c5087461c495326ae1898f8b423e4feb7995ff08aeda248a875f6a3b2e084f5d5624178241ff9066ead72095774148fa4e7b010001 \\x84a82e70f26d13770990c138d9ede53c3f3314396fa23b11afba1b849a04e4db40e67f679f0243c4fdee56e2659ba3fe11700b02d642e29567571a9e0b435608 1645610907000000 1646215707000000 1709287707000000 1803895707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-182 \\x8ec92840dd0b7da113b1bf4afe8651ec90df09705c8d617b1eaa066829fcfba106ab7b4af2c187efdb9f51d64f1e886be135c1dcbb4bdef0bacd15d1b33f16b4 1 0 \\x000000010000000000800003e45134432b9f065f415f1c51154899a28e85916362662d688b14ac7e2264ca4a639a49f61d77a888b3cda81dd5c17666365cf9f6cdb55baf5262eb787ae2964ffa39e820529a96e517de0b9f40969c17f6caba5885e9befce9fe90004e7cfcc6f2fc464f93914ba2fc285868591dc828cca0bd33afb36d04b15fd7c32c524c85010001 \\x5aca22c46bcbaab38bf633cbabda05f8bf719f8474749edeb50472f9236b39067c2eec9a92340d1e129c513054de85413436cbd79512d7b008248c1180a0be0f 1661327907000000 1661932707000000 1725004707000000 1819612707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-183 \\x9451c52d45b48509e39bd81374c5557744d5f403c5e13a6bba191a0076e266f5396dcc8b59c6b9e040e6be598946741b84d5d4e98b9c819d1962543b9c45ed34 1 0 \\x000000010000000000800003ba221906e64a2ccab89b004de7e1e3cdb29493ceeeb9f54694552b14ac2b5fbaf82383d01aeb0261128bd8c23ae978f386b69a6ea2a1d76ef3679b9757352e30529b0eaa99fd1890adf8109605032dd5002923c4502926148e2c4a7971eb5709913aa1488dcbc184d60989c3252c72b030c672edeee1fcad084967780b3f054f010001 \\x78e834aa62f8f14283106b7d6c0178f7b0fd197e389874a77a08b6dff620849bdc5ed1e97b5fc13d01e944d68199d88f0aae2662cf54b9763769fdf099f3f805 1661932407000000 1662537207000000 1725609207000000 1820217207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-184 \\x9569ba34f046cad06ec35f74e0a960cda3c0943dcba873672bba2dea4986efb0b277d8f4487ec68d6e3faae11a93b6fa09af5762b8cdb0bf63b4c8956c946e3d 1 0 \\x000000010000000000800003a6a75f534bc5e36d034d84eac3820b76d8b2f1de3f8810456b270594a48b4032bbc5bbce0d36e9650c39e1ffb9d1a006560afd082d464b6039336da73d0a193b40ef239daf8f411974cb905bee3aa1dfad6a4c4c6bf2311ec1a398412357493c304bac455c5612a04b6dd1fcaf594918c0b901eb1dae97c53b0c6d23914db3f9010001 \\xba6f7fdf9cf45e75240c33de2dd18c45f2e144e1c402037bcecd30c98182506b1ce1b8b9c045b919747a1b903266d4ab66cb215c84044f6d2307b7d9145d7d01 1667977407000000 1668582207000000 1731654207000000 1826262207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-185 \\x9961f49a0ab4f26dc567d883930f9ee1d1e46d71e423aca7920674ec2be3e16d348a9fa0bc69ed700bf710483dc6faf58595bdc73dbfb60a261d88a2c0462119 1 0 \\x000000010000000000800003c792c93d77262aa3e2e3bc8facf6b8a1e67b2ad5f0ae32f40855b6f8aeb0b29ce21679cd675d062ed6c2467e39abb240758ba93a5c3f973de7b13b05822c91e3e089fe49a5732077803df607d6931ed307498de3d390c9dfb17b9bb2eda57a478e9912f16a34abdf4bb184e19a7d6f93cca491c3a4c4645de3b4918ad66d57bb010001 \\x47825a1fdd1081af41f356e2aa0e301df8c9824464214363f8bcaca565b0cc14f4241e23a6de02bd87e87adcb2e7b16601e2214abe87c2202cab80f06d0f4601 1663141407000000 1663746207000000 1726818207000000 1821426207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-186 \\x9b8db333229285b761c269bd75cf4b94caf8a134bd72ba33d8afb4d13704d8e1168339bbd3b199c04917d2b38c652d0d32403269c0fc3cd1760bd29a6b0edb75 1 0 \\x000000010000000000800003c4bce8e6acf6c0e941cb4a7f82e62b240e62d9f9487f7da14b3b5775fed139fdba54338702b15fb2c97727a04947cc2a4e17d1eec82c53cf3f794fbcc4756eed8fdecbe0ff1f811e0e1abaf6f58770761d8f38f0a0dc32fdc80fae61f6fbb6f6f6315e15585c04b3807373b04bcd8ecb7dc51bc43760f216815ab416313cd4b7010001 \\xd1deea13941ba31da3c224c673dd98d048be61fd223fc827388dab1cea1026665ff86cdad5154cb38f55c349cfcdae6ffbff7f04bf1ca732ff0df8c3a4e4d00f 1661327907000000 1661932707000000 1725004707000000 1819612707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-187 \\xa0096bb1371ae97b911965dcb4b1b03080b96e95fd85a187ba8c796aa9a0ef7ebdd89f3d54b374fef4827daa229d26cce958ac625db60302a1437d5df8ed260b 1 0 \\x000000010000000000800003b8785d8aabc06554ce231ee933037b64138911ef6727365799d5f5051b4556f0ba62f62daf3940c7e7857cb921a2b483343c969980fc9ba16af6c7a438d2627461c20e29c3da5c4f44d0e80f7126681ceefec184a1f0477671b8b43bbb7d7ef1a76d2b6de3a39194ce954c03c2b9ba3be2642e9a90e7fcebad27f1555cdee84d010001 \\x9994d80ec769e62e11c8b3af0ffa3edeb6a9fd8c84bec47a5d591068d380283520d7b2125b5f0c4512245b77d2e443437bff06326f371a471293beb8d4edbd07 1660118907000000 1660723707000000 1723795707000000 1818403707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-188 \\xa25540f0a493a3054260c56ead265f0b492df98323e24bea67b01f59e72215f90f0fbe81230df0ccc8dab02ff553f11583cba835036caaebcf19bbf96438021b 1 0 \\x000000010000000000800003af85469b40f9b98e338bacd750aba83657a8b09fdd19bd6214cd26d5da89253535862041c6f4c256c225a21420d1ba438bacc43756358f6d763c088d527334cc0ccc0fc7b88278bc19a518f54b97b260ada2b30e5351acdcc762c851c6730f7a0488af2c7d975eb8a67ba960635ec6d15f58a26279ddf3bc41a688a0050bd693010001 \\x221eee7c967fe7c18c47dddbc785add42220d25c605d05d88a5393508b241eadc2ad31ea5a9b38f6e104b83ba828273f4f9a29d10fe6efb31b595c0b8457f60e 1662536907000000 1663141707000000 1726213707000000 1820821707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-189 \\xa91929be7a78f0b313a34983c7c0f5ab271830f850467e5c8926c89bc53ff41976c2acc0d6f8f11ab08d35f04675033e027736d7655399e0945fae35226283a4 1 0 \\x000000010000000000800003d8e51528ce6d4dd17178ef49cea6148e9d9fa26a979db8eec6d1c4607c44ccb99051d9d3e5f1c2e50021393bc1942ba114c864c1063fc6c069aed16fa0eed5b052d576839d8cddde00982a61e60894700ce9c5cc4ea965190b9368722d3f69f4d47025538e139e6d4d9be40946acb48b11a45eec16faef10b0d8c49e2c17b7df010001 \\xe8ca6795361dc9055d85b538558a2db1b05e6a137cce3325cd0af4aa4a997f46136bd90799ea3610ae29b4ea789287562f96524064390df27cafe964576b0907 1646215407000000 1646820207000000 1709892207000000 1804500207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-190 \\xac9935d79fe1115e173b3ee01063b07be455f89597330783ddfc91f7479fe5358eefebe24c91efe61a15137b5bc3a6e465c5d4d2ae967eb3c9b3b49e1fd4695e 1 0 \\x000000010000000000800003b3a23d70c4420a70b87024eca9cb0b119a4c54628361a85240366d7542cff4907ca05edc32243805f3439b794a5f62f41bae82973fa113d732d8be7623489334e0a2c56693f036a7bc0f96baac08f90f5a51d5db9d623b848cb7a11c2e53b0083037d2f9ecf9b990ad223ab3ad28fef8ae90a57e18675ca8ba7654b865309199010001 \\x129f6e0e8bb2640fbe051892eda82748fbdc2bd1b9d855b4f5efe95727b226f85bcc1cca1fdd8a4cc64fac8c2c627f8780cc3a29109f3bdfb70de2aca6e2e906 1658909907000000 1659514707000000 1722586707000000 1817194707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-191 \\xad01ac8e902b74e7c83a43ce5f87130b7a18e265eb89ce612c6f60ede4ec07f699fef60f1f02674ca1bf4c2c6c159ef4cf42f3fc1c3f1a72128ca3d477448ebd 1 0 \\x000000010000000000800003c588303a95ab215e828b00464ca52dd273ec85d2cd62935dacd03ee706738fadf376ade3179f1619dc2a5000046bccb7e044518c53a22c3175fd43354e25f311b86ef4c47c4098cb155221f64b06dec6b94cd03ed7d60fba73803a023f8b98f0051c25cd97cdaec4e17cb93194b0821439c1f99f8151e8c7ac0711edaff91b8d010001 \\xf45f1be18dc62d9fc176d48278698cbf62002ddc0da1899e52ba403ff00c57bc914d1a29d869f348af003c71350bb18560e559305c5c42364e15d26a7fa8350b 1645610907000000 1646215707000000 1709287707000000 1803895707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-192 \\xb1eda56df8407fb48bcfc817ad0ce523d8c8455e13580b232a616655da731e12cb2287317667f4e86ca50001e0c3b14b68fc668d5769864734c33f8d364925bf 1 0 \\x000000010000000000800003bad25ddda31cdae0352b19ae4d9c9de1e5a964df089b025e0b9a057badf739838d8f023296b8df06b7e6eb11a29f04d1e1b5628fbb51aa9e166e60f1c67281fdca1055a71496b6d7ce10852ce4361eb61c3c7884d12420d800228cca779b742d15d3fa7c21a579c08dd75f5c48b0b97ef89cc28968a2bec5adabc3c953749b59010001 \\x0d47d89ba8966cd4f370d4f5b7dbd008940ac3b1833374eb8bc1f7f6bf3cf71022826d2ab751f8e354d90653fb457dafb37b176b3b1cdd427a5808b379e99306 1646215407000000 1646820207000000 1709892207000000 1804500207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-193 \\xb6254cc2ef251314d79e3bce3bd65c301f1ecf94924463b1c9f96111b4837fa865851ef3cd3ef083d702d70361298b44cb42a47c109622577a1b231302671b56 1 0 \\x000000010000000000800003cbd8fbd36c8bbb948d49de237e969a10a26d905ebb22a3c7dac5a9538e64bc29b37d1c75477bbb4ea542bdaa9002cd45e66e17b012b40e9ff1ea9ee110afdb028156ebef11c758d0d058800a3772907f035e11caccd3d8b7efc157539df08b374876ff2dda19a694b47001bde5c3faacbdfce750f2f030f336eb2ae16e9d310d010001 \\x3d93d5c1074fbc14da93fc9b10250b9fb2285721a9e07c6a92bc24fee2e1a2f05c199db56f34f1860d8f89488ba1577b144424f95b3a478b39b924aa11cec602 1638961407000000 1639566207000000 1702638207000000 1797246207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-194 \\xbe310f287faaf9a81f93db3f942a3fcd420bd70d2023fbd329eb77d761ada67ce377b1667cc560865649b0f39b9158f2e4ffab357a9f2f7132dfb929c1111169 1 0 \\x000000010000000000800003f74ab7f2be6f064d68bfef2353d08277513b1366999ad831c22937f13bf64b137aa064a2f49e6e9049db321fa85650e2f80f984f5a8dc2c5a2cecea884ddbf45b2ab01abff4e98463ebc3a269e0bf223dfa1686b32aa7cff429059a281d947b9f44ffc00c891a64acbbd7ea8c0502011238437239989aa3ca410391565af860f010001 \\x13ee44560d3c37eab9c9f9d76a676e639225bc5169146a76d49df6e02764aa022988656ad1dceece9a82e7a3232450e869007f6408f40b142911078281fc9d03 1642588407000000 1643193207000000 1706265207000000 1800873207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-195 \\xc12d932fdd294f70682fab6bc941018548cd3b9a029fba0c022e3690f971cd5ac7495524e54881be329bede1d74f74278bb5924800b46137323d318fcbc84259 1 0 \\x000000010000000000800003b663038a3bf27e5fc53d28b256547b10c814b28c5b4041be45f725b0ac33267c9afbad1da56be39a4601927b58b081b40486d91937a46421bd597c80cf0d4e6efc024e1f97e2afe176f8c780105d18fc55d594fcf9c349f0340fa24290a8638bfb2f8bc272aafa2aac234c382e9f46611b1fff02a18f295298360bb05ac669d7010001 \\x959fede8585950c509a22f8d0ed2dd2f9122267452003879c395d9510b647508d563224986ff6affa96f779e7e77a20d2e44064bb1819eff32141ca8e30fe605 1642588407000000 1643193207000000 1706265207000000 1800873207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-196 \\xc31916090e230e355a693105b6289ef60b7bb0cc3f455c912e8bf94f035db38bdc9209acacbe0226c79b910a11a367e7065efb232907c1adaebd33b97b04b48e 1 0 \\x000000010000000000800003cbb04543ea74b38d7d45289160109cd3ac5af21ceff406c0234c42b3e936d62fc79f13e7fb33b22cbc289acbfb479085ff1063290fb001c51a3414603bd78e20b925275aa33c26c591d5369d3f41010b79eaf74bb3eea2d0decb6032ef774e00f88634c1b9e6912cd63575f871040a3218809f6c4182532af352bb57fc33a08d010001 \\x32f7b27d2f3bacbd2d07dc66001866c1e334f18ee58648d66abbfe55d665622a7b803a8d0c5993c5dca9fa642648e6191fa0d91b0c06ffb3abb5b41ba3449d03 1643192907000000 1643797707000000 1706869707000000 1801477707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-197 \\xc56db23178c248b92aaf72a057228861ec2c91510fa83b1be1e1afebe3d4625f1e7080e9c49e2c5af6ed22f40e8aa43539f7caefecc4bc4de4f406434e043763 1 0 \\x0000000100000000008000039e3483c36811f629ba2196d23fe7be0d4fdcf3f5609c80b08c18840cae3269c0e98f51ec913eac4d7d11d07fff4c87a875476d33725495330a1385f502fed976b276d6ad016acc14f3dbe8f51da4aad13a117de29e0388999347da6d3f32c755df7c2e75ced27e7a93084d5757f8c71d86767d2e862309bfb586c748eaebc55d010001 \\x35028c8485d6878f6557f9fdeadbc4ee72542ccc4b9e2ebe6c6bd08b2db0f8026f8f8075fde46e3dfcd0d3e8f68d524353424f0e207fac118a4205cc6bcdae04 1669790907000000 1670395707000000 1733467707000000 1828075707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-198 \\xc911eb63ddf4593e2bf22ec8fcdb7c14b110a94bbda5b665120154a658a277af41823dec12932027679ff673d4c044810a99b4c4750041b58505106f7a5c791a 1 0 \\x000000010000000000800003bdc7488e0bc43ba83017618ec98c1cb30c861c4aefe825973ec6262751617bbfbdd953d667b024415cd16265a0adbed7499226e5acc703880d4af291580262ad2bb70b941d330b904889de31100fe283fc2ae65fee94a1aa6217e48d038545cfd59c88e4e6014b0b1bb3e059a1e27d62efc1d474304c01effa1a707f5d178ed7010001 \\x2cea549813d2778703d0e018068a3c3029803b3406d9677386f7f111607920efd16db21337a210e9cfd74fe3c5480ecca28cad8339078e61fc8fbc11b8186d0c 1638961407000000 1639566207000000 1702638207000000 1797246207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-199 \\xcab189344176559c67fe9b69832cefde8c50c24f06505e0bb555d9e03c69cee9a7f512934444cbe117e2769118922d7867c8e1944f5b57dad45f00b0b7592ce5 1 0 \\x000000010000000000800003e4fdb48b143c74665c7f682be74116e360fbf9e7b54b9b248deaeede0c2f60ad1e9daac2b1557d3e2d67fac63149eb01d5f21d81b5f7475f7a32968c404781e5dc2665f72adfbedebc2471677c5b2352e851f18f617d341267d8f14000ad65de47fc34356dfc0b4bcb98df87da25da972f056281a493d41a6c18ce904bc64bf1010001 \\xc2c8c367b01c3d19d460562efb08f192433e6046559ec6b221a1c23d8e7ee5adcefbc27277042cb9a85629fc47ebf8601d3b16f9634420aa7364e827d7b10b0d 1661932407000000 1662537207000000 1725609207000000 1820217207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-200 \\xcae1f7f7a294307c77c78e13b28a1d1099edd87274c2402f8afc9494d19f02c5fb6902dd0ccc2be61e64bfee81cffd99f984eda0a288623d111f7436d4f2c2e7 1 0 \\x000000010000000000800003a19b66d3675adc0029fc68de5d2ed14e0f536f82f9444faef10af20c164157a2b371b32839c91328abd7e94a2b61de0d0daa7d6ec034ce103ed7bb246788f81f3bc45f6bb987d99cfa77d394b932e650a2f635f87e7cd5c98bdc9e3af554ff10db0830e27643d287fdff7ab01ba1cb33abc6e4b69e70a89e1a652d021286ccfb010001 \\x2699b998e0c51c2e6a5612b0c93e47d9aac9eab8e6339e31a00bee67ac2f3fecaf3b9118cd4ca152c06074a0fc6e5e063f20400bd734564139a147708e29870f 1651051407000000 1651656207000000 1714728207000000 1809336207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-201 \\xd109847edf2c15c28716ffcc43683e14ca70e6af7d807506e528d8dbff992db7193f6199578acfefe7b0d4ce676ec92e73616280705d0fab16a364c91b3e8e78 1 0 \\x000000010000000000800003a5240d18b96d39802421cabb9340091b848b9df1eba37b7d34d32baf89a5595738193665d07e7ba7f4cfe9ee4b9635b8b4b62305ed8e763bc24bab6d15116d21aa3c183105bae5ca1e65f11a8667e1b4ddbed603e988eb5ce0eaf51ef69857818bd8df6134c7cb6e4fadc65e162b151f679bded110d8e915ac161a8b8b7c7e91010001 \\x659918487bdb43be6000b7641716030f6006a13f7ebff6e91d2d26975602c5f1ee42501f2fa7e8e3040799430a0dc441daf129eae7ea47bc33afc09ed6bd160b 1659514407000000 1660119207000000 1723191207000000 1817799207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-202 \\xd169281b8ffb6b8992677044f70a8ef5cef056ca1a050d91bc3e298e106623cd2a75acc49f252b0436de1a25f21e1005c9e2894df5a6209c9bb6dcfc20800931 1 0 \\x000000010000000000800003c4ee7feb23063cbd5e66d04f41bad02709f28528e0d9ad6606ee8229269add7184db4fbb000fdaa2b1e90424bdd75948e5048a7c0dc6e180381b2f371d137a9a59ffc497956c49cb4fabdb9bb2b12990570cc1ef6bab6d33bf61882aa52d7abf7be72bfd6f1aac8e00d43702f2f6b35b5e5b9b5a4926e85489201e1c4fe51e41010001 \\xb7f8ad3396cfcafd3015c92fd85f26f229c6ef75a38ee5087eecae29207cda8be81622e8d9e8bf4f200a9c1fd6e2856237abdfa17460f6452a3146eaac80d104 1652864907000000 1653469707000000 1716541707000000 1811149707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-203 \\xd5ad4dab8f19fc53d72e7c9c74a5486ecff31cef8db021c27221e94fd8f2c9973f6c3fc9c4af94918a8ea70470377eff295bc08bb66acfaba5de12674386250f 1 0 \\x000000010000000000800003c89645a167333b134819e4ffdc80d30defe143a9b0178d165a36edd0ea03d6eb4a8dc1761b1acee1908c3d90c232c1d7921f0f912e1bf00133662911d2dde6de8daa9a36872bf05cb46aa5313c4d71426ec102d09d80a9dc1e81905217dd39c4a0d478a543e3ae29f27da9b50a7f90c24cefc26dd043daa12b95264ea89813e7010001 \\x0d6297c12b6d68c515cf180966d900fb45bd06a160e84e6acfb731c8b3b623ce02789c73be32004900184637b19333a3cf1d99730acf0c1d77a740a346fad603 1649237907000000 1649842707000000 1712914707000000 1807522707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-204 \\xd9112270dd7b467cf5ca8da31a72887c94573720edd6a4b40bcd3f43e3357c869bff87e0a4fb0b8da02100ca5f246bfb3eb2b33ce4ca3bfd610dd7f6ceddfecf 1 0 \\x0000000100000000008000039dda3c5983f7302093a61b51a1f528f4aead6c7b9d9d748bf0ea729a10d49292640e29c0bf80f7bd7b9d2aeeda6948040a5faa6ee60bc87de2fda9b74c06fb0ad4e581a9b28e7ee2b8be811182b4e6c8905332f40accd2da7586b86546638f6cef174b154eae46577b7fb19fa4e3213257673904d0318a4be814dc2dae8e0581010001 \\xe6e13b60364a87f16a597f7c192ef2aa984a4772dad28435a996f0f91f8b5d80cb3cb89cd1940d554ba9f7c34595f7499d89218dd57eda34765cba1af6a62b07 1657700907000000 1658305707000000 1721377707000000 1815985707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-205 \\xdb31ee2110df3886c129d528195e115c05012cc8c29d684f8fa2f824e260a4accdfc308932b6b72a780331b2edc871f9cd521f334cebcb41892db7b30ca38c1a 1 0 \\x000000010000000000800003b3b768256321e3d9de9fd4dcfcbf591e857aab71f02e6a0cacedfc859c0b1e288c9458b868aa5b3c0ce2e5f1b67c7471df57602a00b41b4d312b187a5ac4a0c2d2b2e35b9dc3a2c420858daa5c649b0bec219fddd3742aa65e5eed9f2da4b1f68116ab22a929acd1b63af3e1135ac02f78773555ca9ec4c0aa6291583e41a005010001 \\x509e08d9ae41241e9e7c8ec354f521dc28c4d9cd05f2ee95db0951809a7a476969782acf2395eaffec9d5525c48bb2ad181747a19cc967ba35cc26fc1a3b2206 1652260407000000 1652865207000000 1715937207000000 1810545207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-206 \\xddc11b18c5d1eaab5b62df0d494bf3b28fc82f81828c8f3ea7fcedecf71958e9b69a7fdf80cf12cb3bd862d0e381914969cdfb95221c4bf1301888eb54607521 1 0 \\x000000010000000000800003c8d3e097e0b87243acfba162a38a90887555feeddc355bfdd641a2a4bb51d1b8c1581794fbd63bc28b92c3f0ef678089cc44f7fa5d843f6ed4ed91fc297e9dcda5c47103f1bbbda0ab23516feeb5e6d22caa240a149f2b305b3b29b523f7805001efb0e2184e43a5990dd6263db6e81cf70ae4b9ff46ad2ed88e36caa6cd3029010001 \\x515ae5e6c6245a8d33c58380539ae73e68d289aff7644a086131a0bad64ce959a1740e2d15410673776910bc8558c75c41e1fd0ddfef99f5e90068d4b9e6ce0b 1655282907000000 1655887707000000 1718959707000000 1813567707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-207 \\xe1a1dd9014c648585430977632b5e346dd69df35bc96c11622839f4b42f7e493dd5eb3bcc5fc2cf95c53e85b3710a7c0df239eb92e2e23a7cd26f65564a045f1 1 0 \\x000000010000000000800003d8f013b31b3a2465cc3ff29cded45608df4aa24ab94c05ebf4ee1cb38d6e8bbb810eca8b37fb449228fcc2b6447afb2cf456be1a87edc3e96eac0438b6d9baffc40dba97b819d705d13f12fb847e0ae5ef94a3506806d80c65cd7cc1fa453d88c085e90dc60fa34a37732044615c31117d36d20216c918e437b2b6f1adb8d7db010001 \\x8183cb294bdf1b201c87c11d5ad9b7f0088276a8a56e07fc8961035e5acfd15c934f4c26374633bbe399a568078fad51e7c0d837479aad23ac38ea009bdf4a0c 1667977407000000 1668582207000000 1731654207000000 1826262207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-208 \\xe255f4982d9b4785dbc70e1e4f2b060f334b2c90c36380525ac2700b50f0e1e329d44122ea75d335808cec8ff81877786aeb75195ac672205de5184ae6104338 1 0 \\x0000000100000000008000039f9d77732f5606fcfc048eb5dceba09faddbe1a67790b7c3b44be159bdbd92220249900bfa9bb43be1672276085a4f36f9e4ccef5c9508fc469a4a2327b343039d81d3236830488cb399d3ddd55f2a8d63c613e3fbab52425381e547b52e393a04deb431f812200b59b1251d3c63de2f16b0f3204d7cdd03c54c248ff1dbcaab010001 \\x44d566d08ae79f2372e0066010ba2024315617883ff75b7e283fcef0cde2ac92d40f7aadc1cee7a109dc8d5565a4e9f6230024083569c382305338be42b48303 1663745907000000 1664350707000000 1727422707000000 1822030707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-209 \\xe3cd6788b9be307dea9267b4e306c461f9a429126a32ab2bce1e3d9f95ef4e3e548bd6f64b2738e1094bbf19003718de33ec89d9fb404ecc2b0685b11f77a691 1 0 \\x000000010000000000800003a9c042b5f132333425b478312fe94c1514be56cc46f5015d10a1106bd7d49764ccadb2456f9d747c74c7ecd1dfe428c3c6fda6e366f73cec06af290db0a9f05b0b96138dcfc06b57e156e76f79abb472fdf088e09158c716372339e4d95de691e06eccf89facc7b7787d5cb46b8cd25d4c7c0f3a0301b18c06fc55337492e67d010001 \\x4ca4e008af40ddc8af642dae461a7feb1b27f7228d76349fc5c961b4224c59745c399a996d4d5816afb8a757192014c6678ee588874cdf26106b6076d8beb60c 1661327907000000 1661932707000000 1725004707000000 1819612707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-210 \\xe499159b9663afe4a8772e284c16f79fc2e4a7bdbfad78a1c2b0d8440b5c0147cf77ae266124f70deebffbba3541220c3120b1f352674f25acfc332a19ddc568 1 0 \\x000000010000000000800003aca77ed3a57001f1b5acb5995fadb6ebd98545ae4cce25ea6b1f9c355e2252885113d945b8e2dbc55fd4dd46261edbad1ff2449c2f5aab5c73c0b09bd2a7939860433c754e34a9a281ff5f0145baf73a7c2156a91b71e32c6c9e570864904d4aad1432e9e1bf3235826d0d530f7a5b317ec70cd9ded09847102c7c0e838e6261010001 \\xdfd31c90da1e5f8ce16992e3ba14d4d7c45ebf1f92536ad2219728df39fbb8338cab8a52b7a9cb5424f6d87b611cead70a3463aa4ad617949cff03a25e91870f 1640170407000000 1640775207000000 1703847207000000 1798455207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-211 \\xe60d96444f6b955443e17dc9894affc74eec98c934c3b68db954ee11324ae62c9dab772b80d2b6ffd9aaa9f7229c96806b4284f6c1ee6f9ad6e18e5fece5c2c3 1 0 \\x000000010000000000800003d9848fba9984d9ff2070053d778ef7af94629bab191fcc93f15991745361214bf6ea15e0458de68e12dca25e07f6e316c2f70e8ea2f0a759ac908d6796c77799e8a00d4f662f61b54b5a53f1d5514fa3aed968c07e7ab0379868d8c4c2cbbffbaceaee6bf65a15ab2a4b2cc9f348a5a125a23c4b6591bc91c7061c262582a49b010001 \\x770a44a4d0b72d76937e071473b8631d895f70dc1f6b706e3d37b06ef4a09ebdb3d67bbb906bac047d3c58faae4d81ea27a4fd52701b95c02cc6b73c14807a0c 1645006407000000 1645611207000000 1708683207000000 1803291207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-212 \\xe6a1ff563df017dae2134e68521bd21fbb05c44c02005be4865fa8cee31c7f1ae0f368408e0ac773891abaa42fdcdd8b322a598010ca0ab9bac81a3986fea2cc 1 0 \\x000000010000000000800003bddd5da982c364d1ce8f9f4f21a7e4a442d48cbbf69d827a9365a3d406683f950e33d25fdfba2d1e914aebb6dd6f5d58bee1219b6c9f081f2be208c9f3ca0474e68a67334c4385601c491d886ee49bad25e8f5b467ba48c2ece302b385c5c0d6dad697ea4e7eb6fe1c954d1294c3ed1e07083b31b70f8f65592c9604b12d8713010001 \\x5e8cf583bdf481b03c4431bc5c9e92214fe1b71e00253239959f21b71e955bf161112568fb3e7b47e6648225d3034fc0fea7dcf58df35419e00c71a9e0ad9504 1641983907000000 1642588707000000 1705660707000000 1800268707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-213 \\xe6e1b1e4eb7618428c93882f567ee7637b05673755943a2a354d2557b505ff33ad2fe5e5d98e5dd7ae6a5e010582b3b7c7abe4ecf2af71d7afbb89eddf27203a 1 0 \\x000000010000000000800003f9277c06dcafe96c96bc30c0dc654be168df7f85ca4d241ed2f0e1035a542d821aa82d1bca651e2a0f05900cf7d3827f7c39293c936604b41171c83ed42d587194d3d3a4e3eb63d245e72142b230ed4d28f98866b6136a3afabd1c59ab70686afc3bb16b5e01ad515afb3a6438a062723547ae403f48e011574c87b205f2c2a1010001 \\x70846cec056321de8a3de71d37d4c879319022d4822695214e3d39fe5bce787802cc135bf751e42f35aab5ea40e26f47739b566893c193c3d5d084f19a5db801 1668581907000000 1669186707000000 1732258707000000 1826866707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-214 \\xf50985ce7378d191f347f05fe1422f98217b8e0684a0bd91999c9ec10ff482955f38f2f177c618a239d500c35dbc0bdf37646b9844c80954d87bea1398f6054c 1 0 \\x000000010000000000800003b834cd76e4d0474354e33cdea07d5609b36265a712cb4b63a83b3218b236dd2e0606355e3df305c354331326c7afca5f6c42714437ba766d2df1400b59b7e7a1206202054226f21b4d3f2def732bb93d373f87432a30f509e3c45ee9a74438692fb2659ead577e77c53749eeda583af33f4a3f5666495c2aefe313a616d61c7f010001 \\xdbe51c3bd7db39bd721c881ed561edab5f9c35e5ef8909ba03516840d487ea7b8c0e6a11f75bbfcf4b1eb7a7c9ef823e7eaeedb710bbe5335719ab40c9aa5206 1655887407000000 1656492207000000 1719564207000000 1814172207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-215 \\xf7d997f3642fc4468a9665580bb46940f2547f616c2f1f77e2549cf59b4a449fcef1c25c916dfa1b87a64950db674ecb1369336858c9aa0e3fd9bdf08829d157 1 0 \\x000000010000000000800003de010eb45f6f07b101e2448a5318227ba186090f28a81764d3ce3f89e77dfe35967b8d0c38d9136c117deecdf9e332839db5c9268e5eafc487a1bfecc8ba08ac285a1ed617436a47ff29f31a16bede8f657ca47134117c126bc1be6aa52a6c5fd50df1d952714bd40d7ea77be65cdb292a1c3a111cebd80535de6dd046b85f2f010001 \\xb062dcd1afe4d98ef98a639484f64b195be1a202775a98925f27299901fcd16a648cdbf9e4ece63c49ce496720f8a816308d02352543df733407a45d183d4d02 1647424407000000 1648029207000000 1711101207000000 1805709207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-216 \\xf729ec6bb2cc6814ce53d630750ff2ae97cc4ea5311d18cacc2a6b4125307cbf324c8a65da1947849a1f957f628a1b7f7146cf0813221164d331f33d0a3743c2 1 0 \\x000000010000000000800003c42c52ee18b4f7d4a85414881cd2fb415f717a7c6a3f1ca4d43230a4736a7c938056071ebd0580eea4845d950746216258db269f65b411e1be733bef029091b32d3e8d0ce6c10c90ff3c574d4764c86c25ff5c180740510e2b6d70f5e11337cba4af08210cf3ff5736acbecbeeb8fe91fbcc1476dc67e81aba5ad750d1da8dcb010001 \\xa91c59fc5c8a55f8a631c68db2ed7f825ec38bd2120cd0eec4d42a00185fa1128fa699c518e89afc85d6c3781abb16c8205a2875c28f74bf499a14ff14466200 1661932407000000 1662537207000000 1725609207000000 1820217207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-217 \\xfa919294afb1b77bba9ccd73ae5ffecae90a5081b7185c5a46b2f91cf6b16c5a7491967324d2b114a330a7874a8b177db8a5b024b741eb88509bc9a18cd0ec6b 1 0 \\x000000010000000000800003d56c08d05e42e816cb6be8c2ff52a2886bc6f0a7880868661fa26f66fd8f4066c1c366a442e3c16109462c2cc79483cfc2fd1e5c8f110c432b78630823666c62759f75f5d36253b1b757ef7d9b06c8bcac18fc30c9b85bf648decbdf25a74d292cd6ed94ab0493ef8f5178455646e0793bd58bdb469c0f6e10cf4d21968b6671010001 \\xff6000f8687eeb87ea901e764a0db976d929ebe3668c364f360563eeacf95fe9e2a437ba60e8549cd4c461df1e6ed4c8837e785dc1046a193aca6c919e929508 1660118907000000 1660723707000000 1723795707000000 1818403707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-218 \\x00125a1454a77aa34a81240e0a9449cbb1488ac32256d969653156286329dd3f4172af83de57595c68a39aafbf6d2dde3db82ba0b128151d3fcc6c822486c532 1 0 \\x000000010000000000800003b9dfa3aefe5127324b8edfd739dba7bda9353d244dc026ce31bc681ba0f3c56e98978bd663e66b6551d053bf3a23546255319a3b3b04893342a9cb5ea79a7c20ad9271600ea142c9764fbb736b85bfd99675faef9b350f1f79c9ddfee5ce85b9bff725a60042de5ebf5e821f9264255387fd59284741855baadcfee16dd22599010001 \\x91c7bd03a85a33063d41963d61fbd203d5754692755aba57c6f1e8adaa0ca6c580218791cf40194721b64404a15ce173b4d54c2b3d6bf1c2631f91bb0928030a 1653469407000000 1654074207000000 1717146207000000 1811754207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-219 \\x02a6012d562301b8e61092590caa7cd999d3c6b81fac47dc77d4673d341717cf2921e341beb8ced80b62e14e5e2163f4cd421df22348e4b2ea4768433e3b8541 1 0 \\x000000010000000000800003adef82c1f953a5369d0c5563bde18faa756cc94c506db8ccbf699f4c0514d65843dd0326cb891d967b676c9a56770f099e87ea05eb880fe9e53be65ba30f6b30eee5cd470f11d7c43957c57c97ddeda096bd56a4169ccd460fd4e9ce7d346a0772aa17cc1bf2326b755e7ac0a4b651bc3a97f30bd5b1e459359084cb4269d4b1010001 \\x14caecad0b421802af513280bc5c53fbb5f8ae7f046ac89e682ecda738e7bae5ee45b4b2b4bc246f7a0c9ac2d648d843adab6ac0b91fd5f54b0d99bcd52dfa02 1653469407000000 1654074207000000 1717146207000000 1811754207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-220 \\x02fa3c3e9de56e8e79448fadeca0d1725588152fcbf7cbaa3cd79bd947755a0b0ba7bff74a32796fdf98a5bcb7fa996aeab0734010fb7841c7b0e7b99601f37c 1 0 \\x000000010000000000800003c1b83dd2ec46b39ca8504c5aa48d12cd42bbc2c5277110e8748b340ab73ceab08cb247286f090149569ab7e74fa28a78876f0ecc7c54eddb3a75d99d853e0302c96391fa41fa6c5d46d19b115a9966fed822fa8b5b55d585b371c986528a59daf1171100dcecd44bbc124c647097bc9f8fef481041473787d66ba0f001eb599d010001 \\x0f544eb0762cb4d384194d3ff76d5420218ff162f6ef31b8bc370a59fbc7e448b490bfd4aaec09782292b51b5553f6c38035c4b7440167f58766507f344a150e 1645610907000000 1646215707000000 1709287707000000 1803895707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-221 \\x02ee0b3c0929792003d1ff2424b732a48ba9f640e4f2b39fb23a693d6005cd2cecd9bf17a3422d6c0e9ab71fa88813d91598486fde2dcea64809e2b434b63658 1 0 \\x000000010000000000800003d0a697a69d074a155ba320295eef305a31c9ccd20d933d6b3bf9827500ba0fc4c3b0e49c56d6ecb9371a3f201b0929c87f97857d02c427935621204bc4c58c7ac39695f2f4af89e2c35a11444655fae82a57f02f826584bd54e0d579bdc58e8b40a9f4390e3052a0f845ed274c706eaaf87bae3c71e64d514d9120edf63de0ad010001 \\xfca0e752de39232fa51033943a48c988cfaf1518839600500d90627f9e53c1ccd99a16d7f7d9899b809ecae467883d9f6f686a5360dee626c34960cf1a01ad0d 1638961407000000 1639566207000000 1702638207000000 1797246207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-222 \\x045aba5d8a13da62aa104c60f9303c30cc15dc8845e489e9a951585ac74e2031231a86d0038063a47f5fdeed7d2236f709dd39077e09a84581ab99e8ebc83d0b 1 0 \\x000000010000000000800003d217265cc9c0dbdc83eac8f1d67c06a289b22df92b8248396adc75f244581ce5a803ed9fba9c0149fdd259dbcccc9b8d8799ad16457c3e69ff7858f3cc7256b2ded2d9e4acc31c5c07178d8c75cdd4663d19bdc89b81dd5bb25df236b61bcec95fb7af3fe8a9da8ab21b613ece1e02c67a76f5c88c7a04c199a9330039fd37a1010001 \\xf103d45b19f9c1a3811e0ff776c2f773750676aa2bd5f29bda7a075e9b3d9886007c3e9ff4ec8ea80da2832de660aa86a8c500ce2e71aa0a89921e811ea47801 1667372907000000 1667977707000000 1731049707000000 1825657707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-223 \\x07e22bdba146c10f658794f8c024f8633c4e55b6991376be39b36821688eb944da2dfd50a466a79d44f6de15ebf2bb229dfba84c52ea7abd7f2af0330dd0a0bb 1 0 \\x000000010000000000800003d17bba50fbdaadfc7291645cf7ab3ba6e1e6d942c94d1e04888ea59654c8e551df75e40cc037ccf7147237dd95ee0b7b293f338dd7d0d1e6b53c0a3299b5bb442bf2b0b5708b6de763ce9724d3a0320b637db222ecd937ca9a585a40a225d873f6d0170cc666eab435dcb37fc1edb1fc799628abe32149edb8af94c7e9a47c7b010001 \\xb5db937f476e6095d41615677d240255baa08cf99609cc2e2bcc24fed69e00e125fe8c50089e5f832432c2f37c392fc1a3bfacf3c639741ccd920441b0d2050d 1645006407000000 1645611207000000 1708683207000000 1803291207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-224 \\x08fee60f3ef005eeaa63ca6f5905a87ba06286369200192a35bf7fce0ada972595bf4abbc78af36d4d1c56d74adc256f5fe9aaa4941ffde7ff18c687656f7e3d 1 0 \\x000000010000000000800003bd7c04f71b705c7e45052ac47a11c357bc44c14b4d579cf4045f9f6b697f1c17ece56e092875d3a7ffce468607ca147913063df988e8ac4df63448a065c02c5571a2ad353a8aa722fa81b4a1c927a76e3702dc3a52be4d0fb678f5eff7e1b51e3012a6a0dba54bc861dfe15ebae535c07636b6cd2bba5fabcaa61948bd250387010001 \\x33634f0041961cd5ddeb0d4a4d3a1686b4536b0659aa91edf690003addd53e6059a2c8099514420383283d49fc97cf76e34cd09749d2271c6d86fd32bfa9010b 1638356907000000 1638961707000000 1702033707000000 1796641707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-225 \\x0d7262c06fccbd30d6438e53f75b0a053ba4100e2503593c76d3173bb6d493d4d95eb50e8e9324345f4360c02ccff00b18011824c8af7cee94cac196ee98c6b6 1 0 \\x000000010000000000800003c2e8756b3df008c3c7850821bc42bbbab7633aecb55c7ac43f0d9a4249211b0bf9cb35f6020cc219890200a1e141a81e91b0e61c56363b94bb7b0648b6c6d114102df631db3a0ea50600d16df012776b6d1af70166ce2df9456cd9f0262fb349f71c88d78ab0918b426e79217363336c94271bb7239ac537254526204ac81387010001 \\xee246452d53acbe4eca8a68ae7ebae8efc0624f0cb2cf2df16a0f6a53077100d4a602e4874aafcde13defd2f0ca98b1a4c6d506f21059615e76f233951ed140c 1650446907000000 1651051707000000 1714123707000000 1808731707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-226 \\x1386f2b5ffa41410cc46b0e725cab3e4b8c884bae6fd732794b9ee41809a0843df35a5cda11792bb819286bd23b81a092fe6c113b5a1ec27e975d08e865d846a 1 0 \\x000000010000000000800003dbe6c736e999be5d0af1dc3484fff5493dfd6201ecd042176bf1ad4ba38dfc2115eff74958dbb6ad4ca07910cd1dad733121ed21d757c1746a315e2b9a890dd4067c6e4d420f388f7591493fbb1e494594ad50957ce46dd4902c44a446ba37810a76731b94692d117a80ae9bacfe2d8e15f933f82342d28a9e0c2f9060e37d0f010001 \\xc1880d387cabb19d1d4a793a4906a8db6b981d2b07381c1b377e6f7335006698fac24bc1b86de30ed76c8631394028dd7d273e43d6dfe70c3d0a5e697f493b09 1662536907000000 1663141707000000 1726213707000000 1820821707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-227 \\x18465ccbf0ac49c668ab384f4f4dee141d317d38c9bf8019bd01b85afc88d20355785fb30897f41214664368bcfb6555f75cb3ced2a5b13a21756ec0308575fc 1 0 \\x000000010000000000800003d770c2487079ba2ae8eb9e99bc8ce71c3b975b570fe09fd327b67c4f9eba6ba812ac81e3a95eed14e9e3226d55cff4cb38ca3c184bf42f46c3a0b00bfd6429450d73d0ce585cf6f9de17618ac35048e872005ed8a186ad1a197b0526a78e297beda4563156f2b1b67577d37d90a689dfc64d33b81665bd1649e7df60082e4ec7010001 \\x1e6d611ed44bbeb53540e6f219b670ff6b323e9877a8f617c5dbfd91d59261d20c843ce012a97e990548bc91612aba0dfad0e60375d518a9b0c02c0313844104 1651051407000000 1651656207000000 1714728207000000 1809336207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-228 \\x19ea59f9aeb1f765e7a61582f3759bd4f18ec1c2003257a8351e36bf8b6655f2b057e87858d8a96cece03a7d3c4ccedf5ca7559bffce7090c151269e668a717b 1 0 \\x000000010000000000800003b8230dea99f666f382d71d274276038de81ebfb5e35fe6cb1ec65840fd762b6298f7408dec21c0c14dc3d4f118496feee3f183a72e2342eca4d1ac51e35f24381aab332d14621038f6f3309d12577b21107b6137c7f371dd45e43f4b50c8a78ffd4c9479d329ca81d5f5f5f2df439a7ff87b4b31b173d6980f7864dd7592ff3b010001 \\x8ef8cb8e306fec81c25512c80ab24c20a563fe130f2fbc8b1c64291691b66de6b960e9b0a368d5d538076d40b190f98535bbc1b38a95e5e9590af986c8827b01 1649237907000000 1649842707000000 1712914707000000 1807522707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-229 \\x1a8ef1e8d8b254a98a03f43a0b829942a5eabc897cb6b08b02e8af70d47cdfc1205131e6bb3ef85a235d66be7bda56daf06cb5d7d355b7274e31ca9604a33c61 1 0 \\x000000010000000000800003d8a7d7af503c75517cb738f4e51942070f7b97b6e1eb575b6a0ea3c5a6adbe37dc1e0373a09bfc49ee15d36e3d8c4751eb3ea428f60656b4fcbeb28cd800cb66444ac45dae5014481c8db2dbb4f097ff96e20c4764dcdd8f461c6ba846c506ce4029a7e4f71fa891b4ccae4a2444d3a40131ec536548f1a5131377ec0760f1fd010001 \\x7168669f8143ad7fda8a8a61f583efa986af7f20137729ee0f5809890420d570c80a54cda30e9ac08ba39587a6f16a034c84d7ede70210cc6249ae44217b7102 1660723407000000 1661328207000000 1724400207000000 1819008207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-230 \\x1a6612b1b553fc142172379ee64db345b50cc413d37f5db2d80a5b573c9c687a1644d806c9512c5cde734b7286edf65dc9b52d291842e8831ebbcd35964adf6b 1 0 \\x000000010000000000800003d9ea53e79fccb2e652ad511e778a30307868bcc42f9467581fd828c8821c95dfcf6d4834b0ff1ba181e9324c73122587c7e7d89c5cca6028ab30275b789ee2dc69ccc0ae4c3e40e38b65fe9eaba865ffdc17a1b2fab1646b9e33a67517cbddcacbefc1a202909fa5792042d90e8c0ee5dd5c6be422d31c2168ceb474ece48eff010001 \\x3b3b10918c1af8adbd2ea015a03f6728118dd5b140edc73e25c84e361832ce80cc2c16f76b128265feb5a2f5fb282152b9d28a4160cdc7244f470b16beab8708 1645610907000000 1646215707000000 1709287707000000 1803895707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-231 \\x1d6ed76d27239a844deb4cce5302c8bcf9dd9161ba88d73d5ba2a5cc848ea367c51b38679b64fe7139ce39ccc9efb2eb1e776fb222afaf20baae731a309094b0 1 0 \\x000000010000000000800003d7c32f8984b0d5966b6bdc95863b02ac40dcb4f43925c0aed1063a7b9715d530d5b5d55838d5102bdcf4551b1b92fe01ffaa118866f7a6fbf0b38f5309ac887b830da3acaf11ab3a0b0dbe9e508acd6883b55646a9eb9d77be6364bf3e25ee33e6496d12cefb4847ff3ec4f684a84084cc8fb6af5d493ae7d95e393c315cf583010001 \\x6421fee55a46288b89cb39cf4bf2ebebe59cc37361363aa3e37438af4b83dfea91b0648fd32173246efd69b06b1e17c0fcbfb0206f9b1ed6cb3ac65d838d820d 1669790907000000 1670395707000000 1733467707000000 1828075707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-232 \\x1d123f3ef8e61a069f4b85115a5ae378d386f767b575204e76d7b28482497d31c0860e128571af711cbb82b1d23669d0a8d9b6d7421f85efab33ad2ce33b2920 1 0 \\x000000010000000000800003c90c299fbb4b374e3c6221b88b2470a684ea2cfb9cd57fdb1d4a9be5635627daa22fee57e923f2e7a5a24052367eeab4854af326f70b42f9ff18384b06316ee9dd67506de6ae1171a26adb0b5f332042b08979538e27f82aeae401a0fbf7e3948169df89b8a2bc25bf0423e5e0c2588a466569740c632cefc063d7be90a64cb5010001 \\x82de49b819da356515146d22eef202ce71bbb3baef71ea9184445985459321de0d9017b71f3a29b2a868aa1686060414078fc6bade15e6e34c7a285672faab06 1658909907000000 1659514707000000 1722586707000000 1817194707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-233 \\x2266d9aaae00a3d8dd33b76f9f288c027f9c6b9ca51000f3e0b01e27a8d25708b6a201b29dcda02bd2f059ce72d741c2b7698be36cbd74e61252b119611b2763 1 0 \\x0000000100000000008000039fd006cbb0c907220480d4bf4a6313d0dbc5b7545d178efe66cc332e9cb1ca145f3a04abf6d1ea0486a9fca8e60e3400065db985c7ac0e159eb360f24c308faa6a9636b6a3daaa14f76e65b9045d567018dd88b1096b14cfe46eec86e747ce5f2330771071330f9f0166d4ca69e1ec38bfbba4d09774129a7930d7d9c2977c59010001 \\xfbb5839f59198052614df9eaa97f0eebe1390cd9442d7cfb3ea87b139e4962866df008b127b5108ae7e85850c6b753a855624b23c661af81056c3655048d2d08 1650446907000000 1651051707000000 1714123707000000 1808731707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-234 \\x229228f81b99ac313290fa5d924ed7a51f822afd024585b1c5aa0a17a719fade0d092d99f1825dd835943f840a2a71279916d1cfda1e4bece80edf8b7614120a 1 0 \\x000000010000000000800003ce16edf3faab14dc280e2b8d36b4f8e569d4097a0322479bd860a179df771c69858a90da07c22b46ffa5141f90358fc6af77621fb9856a6a48e7328135552570487fdfedccde48a85ab20e9a58c208a78481a3ce2d0fe5ae4331b03d0c591391746517f2c3675a0350accb50b7d542483a96451e1397058fd5df35c606e63671010001 \\xf12c67a1dd176059ef4a5e540a370ec124c835b0fcc303599dfde1c45df4520f88c5302c3adfe5a625a3ff9782e6eeefa96b1ac57a9c91132c3bf0e267711b05 1666163907000000 1666768707000000 1729840707000000 1824448707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-235 \\x23ae1b32e5412ac8d69350a683c3ca5e8b079f00f3256c6a3ba9d3dc97c8df78814d8d2fedb8a978c00560fcc8bb9bcf3101fa56032db6766ae1f163a18ad45c 1 0 \\x0000000100000000008000039900be5acb85ee1002cb1088c63d004cf91fef13da06a5800210b94b4304156e0f16121cf08914e3e4911168351868dcaea43c6b5d34244712e8764981a5f91715882b5b6462edbab15b473922f3e3fb6afc8e5a0b495a7554c6e3f9ae5b9fa40f35948aa7455263420e10ee5247ff87d3b3992ffaac8393c6d5fff2e38e5713010001 \\xa4522c71aa0eafdb53c13dbd176c42fb96e8d8e814675235367b7558bd73506fd221df7e8a0ce531a60a9dd201d55dc5e175454f91d903fae05a9a835e13bb06 1666768407000000 1667373207000000 1730445207000000 1825053207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-236 \\x2412ff56a20e86bf1099ff9efb9f9caa77f27c111d0ab250e33a460cb409f19c4cfb3a4fcbd9103d0a23c3f2f991535b2437177a46132c7d317b8d47b9e55fac 1 0 \\x000000010000000000800003d6af246230417a2a2f56e77b4c02aa1b2cc9b3a3c7787b5ddb27a172453a4e7745e18c1f8c89ac451b8b199e13e1110905cfce09b9bfc28ca43cf57a19c9fd1dc1c72220642b6bc6167a29fb205bf292fdf6fc200e640d6d423f85bab6c192cdb8282eadb1ba45e0ad51bce2f656dcad8220143c85571bbfa5b8940650f88fb1010001 \\x535e435646e7e13d8956096d8e4943c1de1d6b447f6a9c95c2a177c75f334295471b1a1577c58a4cc4826e8c50a6119a7eb575ed66e107b08d68340509d81b0b 1645006407000000 1645611207000000 1708683207000000 1803291207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-237 \\x2582ac7a4c2d856dde3c631327e096b620bbeb5cee5f9a3b7d430e82ece98e8b072ccd97b117ca027c5274790ee4218490c60216d8b91f8b7122d38ffcae35e5 1 0 \\x000000010000000000800003c9822bc0cb70db7eae7120c6924d834294e5c3bbbf7ddf5b8f3aa55745a2eb6c0914497f7f5b5f8a656598f382cea07663d24a0d5446c6f438a33445efad0c649290c27f615c5db6956ea3dfe437d984e0d40eb2b2913e38b4dff92312ec18c602a861aa85ed241dc3e4ef0277e1da7b32991670688f8012856c18dc36f32b0d010001 \\x6ea07d3f7ed949369f53a18265e42c0049652cfe9fd743dc3ae0a5a828ac9846db5eadbe8d0b7ceb028d833d8e65c4c37ce0d0f55ae7205c6c1c4acd9e58fc00 1652260407000000 1652865207000000 1715937207000000 1810545207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-238 \\x255aa7df8dec63d0a0117466aeb1412ca2d7c620f191b73ab6df8d3ddc47caf3cfbd3e051bd675bb696a8581e49b210be7a178787c579af6e50a6b70c43b1d41 1 0 \\x000000010000000000800003ab5b2b1cf300672cff7e7605d47f56bed0db0f2361198f3fbd870e421ca8f470d50c3c2b9aa8bdc9e9d5e8597d6bd49c7eacafc888040b3c33d00ae1a4762198634cc16acb1ea67de5cb0a08dac048f9bd6b6adfcf9798dd0b1b4d3245fe78bff629f04f0e1deb66b330ea43acc629608286ab6b4e595f5b9aa5fe11efe3fc95010001 \\x333c05b1e8d0ca2c2afaa7ab6f5e39adc2692ef3d84de1d57d744b2961090e87860f44830e73d6c3310d3afd3ce8c16661ba8143fea2d681ee126bb389dad70e 1645610907000000 1646215707000000 1709287707000000 1803895707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-239 \\x2656a93873b6e4b86fe608c42446c164322d310cfd41ef418a533857d0e12131325adb38f7c61310333a463681804bfa41a9b02b12e6b53bc5aa2e8cb3bce1d8 1 0 \\x000000010000000000800003b180082bfb7a70a3ef3f107e0b98e8bee491373802c777b02b2d5e3e94ba9f51f1776f0475a578e7990ea30f412c4e11c1997d8c407f7afe02b36479abb6d13cb6795fc41f900dbaf89e27e605c0c814bba64bc7f7a3d7f052e81424aa57b14666b3b9d9afc3f3dd477597aa7477168331602987bbc8d6bca0a4261bb7d4fea5010001 \\xdebd1db33b8349f376bfa93315f66de5bbb94cc26f271b3bcb0e62e7236b37ac4dc544a3d4b3e2008f709dc974371ef9d238d658f1097db9f4f6e2b100635408 1644401907000000 1645006707000000 1708078707000000 1802686707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-240 \\x2aae53aaded67407c0e02335240200a8b8fe7e4909c4a7f701da0b82297ae79e2e63d4472d927894a48257619ad0345f07df0e86fd392fcbb0f480bef4732e14 1 0 \\x000000010000000000800003d1146af35d4a81ad37d523af2cbdcc4d8aa2e385e75e95ea1a2d3f00ccd12088f72600747edbc19cebe731f5dc870cf8268ce1467db07cefa858e3aca79317bc07da3e7c160682b934cff79bd61a1db0a9c8b1660d0b2673e316bf2d5fbb8d25b543a3e1cc00b3dce6262a120a26e47387bc5fac34b94b2d2c12bc83c6045217010001 \\x8d506ef177c1e4b4cda748c4f40463bf7a105af73613bec170f5985521dfc6b7612e198bcb91f45b956d5255eef6fd6e2841078553431726ac6cc68e743e1309 1641983907000000 1642588707000000 1705660707000000 1800268707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-241 \\x30b65884f51848995ab1e3110047ca26bbd37a75fae5f1f87f0e1f495e06aa76e1fe58aa2dbc8c79c9fe617a423a994d9878d6c7833c086827446ba05e941d55 1 0 \\x000000010000000000800003a241864322528b508c8d4f6fe9f10e41aba1fd1f71a28f47ac1259fd2f0ca7be375990a3498d38f2705429cb4355d3e8f1a1f1ae8060801d826331357c9498ef21c771ea65b097831eb21d66bb37f2750aabfa2c15aa3b5619722b8783731a406ef2ad881edfc256d4b8e743a3bfa33282051f9c5da47ef9f8ed892b820e8b69010001 \\x89d9c7d334cac2819b61feb5aaf84f31a1c66a330a95649f92427a16b4326221b8a475c7a6871ab68666e25d4c5cfc4725125f6fc91bafa0eeab0391d91a1b01 1663745907000000 1664350707000000 1727422707000000 1822030707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-242 \\x329a4ad879f7d857b4adf5101cb6fcd65adbe1cf97ff01e37c5f9a72133b331e2abb538677bc2c12d06c2486b1a0ce1eff383ebdfd905e499b2ce2f85fb51082 1 0 \\x000000010000000000800003c63fadfc2c21fbcf9653a1325131ada17fb73c8ae5568dccdfc7bb70a9cd34fc25826ede47f7d07c605f82a536545517782b1e5d308ceac2e8dfe51088c8e266ff0369db5ea7d58646276c9952839977fc8d67f3ce6108a7ede146ee8c86640ccc85dcd36ea85a710881b8cda6d32c515f25ad9f2bfda7742cc5fb5606d8604d010001 \\x893ffb046d6cfbb23573644870f913441b19f855735e09ba25c99e17f7b5ed70f4afe6cb49b5fd5cae364f59349924eb9fa75a9d647b68fca95201ccf8779800 1668581907000000 1669186707000000 1732258707000000 1826866707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-243 \\x363a5be9ce32222dea1bbcbbd6a179c07e3b2abbfe7bb76c17e1c59d15f9b429d64a68ccceb3c8d1bd40f8730244cf514c2ff3c7bb98baa1200af1b5ba9d81af 1 0 \\x000000010000000000800003bd90f105d82bdf2ce6389261a5656f104b1e094e00e0a349ee0e85c20e0a73d197f3ed998ac8ba9cb05e244b402341c0b52776086eba53928a3b4ec5ab17fde240bb67bc999841aee2003ac8cf243b9f29ed66b70063c8c92f54bc9c3a523c15c79522f106c9f400d17e4b24b792bf3325ff74c2ef1ee4ff767d2dddb91143ad010001 \\xa5587de4f1223d476acabbdfb180025df06e4fc6ce8ff4ce926d48cb3464116eabccca7d5c550f06370b58a28613278e4d41061a87cda198b9f55b0d806fff01 1668581907000000 1669186707000000 1732258707000000 1826866707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-244 \\x36627cb3c2c3757cc98146d0ce481899826f4e3c51fa04f89aeab4348fd502fc1fd72fb528a88d6edfa7956565fae2e896f1e54ff952296d61561dfe1bb653ab 1 0 \\x000000010000000000800003cb13b59d36d5e592e9c6ca7105975fc4c722a82fcb6f56062b4a646ab5a43c602e48b0eaddcee86c1193b91abeab06e22268fcea0d447adf75d179402e88f6552bb5be03e25306d4a49ef73afb93a829e1767ba7e844c812ef726b174bdf441e745e8230bd17132952bc80432ca5594c6d1f06c6b963855ced3d9fefbdf82fa9010001 \\x3cc4a4b1f794ddd168e37f6b454c61e8a36cb366d89ce964bdb3b696c777b962c02fbc0c8ea775b48241d88a709fc4be9648270aa10e8f6ef23aefa3d2d11404 1667372907000000 1667977707000000 1731049707000000 1825657707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-245 \\x3c96ec46ea981ee4eef41e8db45be58ae342112ac6b872c5060a0cd4630c472e0fce197a37eac5c15b2defb531459aeacea85c669dce134d6d523a3be0556191 1 0 \\x000000010000000000800003df2b9ab4ab3ae39e8208d9a0f9ff51cdfc807d414579a854afaae3f457be1e93d113728b66f85122e40180cce12481146a5c3a33e8e7c7e6181f4477cc7f15eadf86086b1c52042d66c808275682951e0012af100666f0b8969d8856b5daecba8de4475732edcf52ea56ea4ddb3f0a3835bb8b9122dd4fbee2753a60b2e78079010001 \\x810f486924d5448d5853d8d0c165fb94c901dededd59e509552f500692b73dc9bf0c058b967f447e68b26ccb7db6be409b39562bfac66983d9d0f21c16261d02 1640170407000000 1640775207000000 1703847207000000 1798455207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-246 \\x3dca7d62252d788302b98d56f411a5e5c91cdc1af1c9348d7f42dcf7a0d9f801c2340d3aa690374295ab89dc32199102ae3e811bd9bd7ef37efc31589b53586d 1 0 \\x000000010000000000800003c09c44092da350e833074a0dff5f22c8e1048462f19c7a10b05b88d0a1c7b755d2aadc46db7f3a5dfe9bd4048db53b5f140e323a17319b0b0269dd2dbba9c89d35ee8ecfc9df85273e196d528375e02c7c1721fd3bfd268622bf585d2e021da6eb418fc34b74ac83826792253235c3adce0e56136f730521eb8442bb4b0b8c37010001 \\xe6fd0a52f090799c6c5429cb259fc3bed380330205f1d02be91caea0182bbf946cc23e8789009870a5226e715a4c72a408803b435ca8011c82250f67c8b7a604 1653469407000000 1654074207000000 1717146207000000 1811754207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-247 \\x3fbed50f580cd1c267967d9e7bc82ce152485e5df3fc5a5a30195df5dc681eafc82a1ddc9f875946a9150cb930395bcd562d2db6f81299bd49f60547661b2bc8 1 0 \\x000000010000000000800003a30b76ffe620bfeb5a86a41fe7dba17d771e2340c7fbbfbc8f6e5dd74206e87342148e7ba00e44b9a9d07d8d001169cca1fe678119f0783589ff2c0a018dcabdfdb281ccc3a8780a2229e462b8d3b23587150e9b9efb955683574e925e2b1811e183d6ed8275c3cc93cef4769ba39c7b8b35449447cf8750959ff52e18e42d4f010001 \\x966fac4c7f11784e4cc2ab4f10d119b9d808e85c3e098f7711bafe49f7c66febc033e32e055e148b401e8c7cb3f0be4ce366dde1b5cf6d0d9332dcce61e39c0a 1665559407000000 1666164207000000 1729236207000000 1823844207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-248 \\x43aa34c61acfb5705c773c73c6605026370dc6829583f84819e911a39e50a22796fe16570a54ae6b8a39cd77e56c9dc649ac924f0a0e4590189b567d41b39f7a 1 0 \\x000000010000000000800003e12eace89a501d611388caf123b600d69551387f553d80d05c5037438f6d16b953b4c553c94ecc7d1c8df238f525e327e7394212d93b34ed28ddf4c52cee963cdc5d9461e958bbfc73baa4caec7dc81016978943f0d0ec56322a4810b1ddadd016014c19448b2497765c5f78ae052b9aa173caa1e7df24652573798a4714fb5d010001 \\x58171e93a7988cd691b95bd6839238954faabec11920990e767e74e8a5558e29e6d77ff04f369dcc5ad2d193832e8c5b4609da5a42a6a9461b58f966813d780e 1664350407000000 1664955207000000 1728027207000000 1822635207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-249 \\x446a7b9ce596e4d5742142d8a6aebb313e791742b494c6e713f5f7d9a10cb076a29b24097a2e0fadf01a739057459e14745eae6196dddb87849d0728ae336877 1 0 \\x000000010000000000800003cdf719f6a6b23473415ed388ae302bdc570de16ad5387ba40b6c66c032ebd22392a18c8cfa88d2084979b2906681b966a6d604bfd6fcd5f9aed04489a3f44d3654272ba79180d4e30c586d19b4b16c296be5964fda5eae8891c6d9ca29bb4d336da18656d3866511cd392282ec5691d4c8c4db24b7fdc78b941ee05d3ebdc5d5010001 \\xc74d0f7bda350ddf9cc088669c5fd6d4ec247c09b13bac028e7fbd119b6ca6e72660451ee8694b8c18805897ca7371c4e0f56036d40deb7b04077575bbde8708 1654678407000000 1655283207000000 1718355207000000 1812963207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-250 \\x479e7fbbff21e25a76188b11e621e3aa4806dad22e229563e13413683f50d13935498de3ef40c8b092e2aebbb624185a53bf5947c9cabd7291d516e0e488fc07 1 0 \\x000000010000000000800003df5ea28d322aec5c5361b4a285d9bfb5bbc381b3cd3e7185430febb44a479e5d46fc5475020f50b1922bb6f84b0214fc4230a1062473cc63851b556aea9252498edc36c89c3a3e143ac3bbc6ac00bf07315557e0b14f8a957654f011d4bfd09c9bcf7ec6a6bb4d8b40059fd1c0df8f37558a5773ceb07f0fc993d335ea56731f010001 \\x4ac8ce9fa9163663306746ed1d5854e5ed3cb8a65554210e2d18c5cf8264d308206750fc0c39fe251799503b2f734c5f23745ece108e5273451b8e9dccf1f60f 1666163907000000 1666768707000000 1729840707000000 1824448707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-251 \\x476a85e871b21620046fdde1277b96f9833bdd09d1878c68399804050cd21b3f71b0eaf546eba871d44e0dcf8cb55950e97b5e42ededa0b32bc958afb56de9a9 1 0 \\x000000010000000000800003ae831be3b9ec78a6341f88e753f4f490a6280c43f5df0adcd833ce58283d87f8287cdf41c9710f731543289d32423849e2ce24b19fe760738b1f0599bc9e8b53ca2b0e1b1d796116b3e133d55b3d012b0e955f8416c73f92ef2a44de278a758ec9dfd293857ce6b4fd07e4f9ae39c48c4c9fade634f6afb66036df107d81f3e1010001 \\x351eedee57b3971147e07d4883d7306a27cb177447bf90fbcba4b009b7004bef5c29cd20294f1d5ae55cb8c912e94798bb50f525ea046edacf9a06178b589c06 1650446907000000 1651051707000000 1714123707000000 1808731707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-252 \\x4b228fae8ee419be3dfec03dbc097fb1ee3736561be25ee6c9bc205a5c369e7e69327380e80911ed2d455783d59ed2d007418cc9246641bbc268fd834e70ad7c 1 0 \\x000000010000000000800003b4eccd3b762d2e7156ef7ed5749de58a34af2f8993647ee45d511c2db05b9d4e4d7c5de2663021e7a6bfb262cf933afa8f35589f7e6d37d33ffe9cf5ad9d3c1e3022e66c2c5895e25619156e4c9fa2b1401ecaf383b33749835949287361097dbf24c451b01545339fbfb752a50460737f029b43a2b69c3e2b140bf3eed79a8b010001 \\xa77d043e3efc8424dd03f826647b75bb695da14a969e8b30aee7aa511669efb6b15c23bb55036b1f9cb3981edab2b3338676e52d5e7e921f9b7dde2ac7636000 1642588407000000 1643193207000000 1706265207000000 1800873207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-253 \\x4f8ab01cd09e29c2d3f5ccb26662a313c8c1484ff59e5fff326538d4e3c770a1a6558026989a0e1db541a26796a6e001d0b393606326eea7afd2bb568f11e3c8 1 0 \\x000000010000000000800003b1bee88b5e4d32c42ad39556dc3d2a4b29e8588106f4de3e6647547f58531c4601a200774f0bb9c8b4c47c890e63b07885e6a3583d6cfddb8ed395d0a2f98cabe4bb73863e6f558938051c51a8578c66984054349fb56ec719388e5f7060a216468a82d54933a658bfc56acf8f3064ff69c545dda3d5565318a8e021ebd0da47010001 \\x82793f243978c49f73d53ff313b174a6a6019f4e3a81a3f1324bc90123616dd97d7a5148680e5a285b58d05ee51100e43606d43c2a4ca84a44da4574af77ea0d 1647424407000000 1648029207000000 1711101207000000 1805709207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-254 \\x53869db8700743cc814d09f2d797f9aea682ac2b26ac5c07ae81501406910f0e29d3cd510138836aed14084cc103ba7619ba7bd9373465bcd939448978af257f 1 0 \\x000000010000000000800003a7a8c62dc4651df44136d84d6d36f6cd34ae01d78cdd901b4be8a341e8908dd0ff7f1fd86736df5e993591d5a3b50850ecd3655073e6457aa0396ce0f768b2e86d9847a4aac8f3ca774ab1eaa74ca2bdc358538cc5da9b1fe832bafde5972b91b736714cbd17c49bbae0b21415657a608876ec6da12e06045a59d901a01a6da3010001 \\x146425cedf8c65485604b10b9b1bc2eb65bdd85418cb8937182493b61376ce66f720ddbebc304f39179aec36a20a8af3ea965b1a7201b320c259e656290ed609 1660118907000000 1660723707000000 1723795707000000 1818403707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-255 \\x53fe17148aab70d6517dc2736712746a28e6dd82bd01f192accc1d031754960c178eb2b95f20a25ebdc16c219212887230b58b039fa78738d9aae168a14e0fe8 1 0 \\x000000010000000000800003bcdb7a31f46039c38131a838f8acc6c22a2a3ac44c5e631359c7422236aa421a3812a8e1ae65747045a5d894b05a398f9d6fcf01c58a1a2504af22d5e642f354bc4433a68226488cafef1a294e5ef5306bc6e6f86c146313dc9c97e34a8a114ad897ff44784c5805d6d0418ab8d85e0d23d46c9a5815ba6254a3f16098ad8f11010001 \\x123208b53439847658aec94ea5a547736cf1f209161e85af94d772faee51ca308a364ef8931f2ba58ea11d5a2119e3ff49e564a3fe8332f2922e7e2bd8ff8508 1656491907000000 1657096707000000 1720168707000000 1814776707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-256 \\x5532432b0619791689d57dc04e77b25f3a2ec3a3b4b23a84dd9ad6ec5e4abe45d98fb8d8a056ca20e3b6105acdcd6976780b5212f7d72e646f36a43b8c2ab10a 1 0 \\x000000010000000000800003d6d3fccacc61e11fd884aaa896f6da2133c2fff34ed0f65b373daed0c7528c70c39914fcf10d9eced6915b7e10424d5b2aa04f277013b23627ecc8a4c8964958dd833977a8ada477f4be1f3b78728a1ec1b3cbcaaf765c56cc5bb92c1e9c25a35c594b5781d52350c48888ce7a290d4e27cf0f14b032b36a365032b6fbecb169010001 \\x7ea659a15b1aa859027b0e7c746419e8723f65282cd505da00e1855fe7cb346b0b7c0f97f3f1a26d5df3b4a869697b18f5ec4b6f56e096d328bae16e39e45703 1638356907000000 1638961707000000 1702033707000000 1796641707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-257 \\x553e0ff88a02120b92e37b4867974e13a830773fb596e48f0bc86aa914897db0027873b7cb14df0db2abd623593263c6276adfea1810bcd389fabaf10ff3b34f 1 0 \\x000000010000000000800003db7fcdf9e22f93a98da0d16ff8c0c98c713be3c99e426f36bdf0169f7ae217894445cfeda1773bd252ca60d36faf359bd0b5150190c77cb83c7e2f5d0b308228dd13c2f615c426be1dde28c71a4b4a5442264c2793a3342e7bc0357de2ce7b2e021ca1fb43a621045c5edc49e64cf138b93e6bf558e9fcc7efe727aae8882c97010001 \\x9d3b3c51ae3c9db76bac5f8bee132fbf1af65dad18202a981577df9c22ca96c114d34a9c6b1fd7b74e60a3b85eaf29e4e63d9553d4b2b0e11909bf7a3d512007 1643797407000000 1644402207000000 1707474207000000 1802082207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-258 \\x565e1b3473fbc5f69bfd45eaf2754940013de5f65a48b2417c8a879e8580226ddcaa3c80f841dbe16800cd9402b5ad2be9f7db8a0c94f4ac966e104807647810 1 0 \\x000000010000000000800003d6f3a97837687cf39e4e4c3333c4f0a2f15bc8ef38170ad7a3bf0a6da3747eeddf7894815ed06ae0f1c23a1eaaa7a0ff37a47a8be6d4bda84bbd80078f2bd2a727a0d5bdfba36f92ee6e45ba158353d4ffd01441be10514741ac958bf9779115fde77742a883a576e3b4bf355c184ede207b07199ce37fd6bbd43a8bb1ba9e85010001 \\xf7d30960dc5b575f9a2e900082a433b15e6c69c30779ee452cad4fc022d57f54aca6bf444242de06fdbe18c2d642bb99ea285e1d59d35c1158cc64b8695cf80c 1663745907000000 1664350707000000 1727422707000000 1822030707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-259 \\x58629f8622a7efbe75afc1c77cefbee983cd24920d436c9b8abd809ac426fd1415b30cab0138b20f990dfdded60196511cc2f7793826d7ca693df7ea862a9cc1 1 0 \\x000000010000000000800003ba361cf3eb28df0d06e889b9dd1eaeb11d7473aedc7988e45965244ab025f5658ff22708ce819212818d8ccc8d44605425552237b393a22ed675b39d9a8479bde3aea56c3729bcc2577c4663a322f9c0638ed9d304ffea664d7fc66c2d5131d360f50cac63184991f759f8e4dbed7087c5788a2d5f80159016ac42031368520b010001 \\x3638869fc98ac761ed4304ba5b76a96e70910f69ac571edf2183be6aef414640b83ce085516c9eaee53d69e4de43296fbecbf1b65b3aa04ff37253a4bc8b8802 1654073907000000 1654678707000000 1717750707000000 1812358707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-260 \\x5aea9c80be13587078bbc8767bf78479a4c778c8710d2babaebda9dcef3aa1e6ee9ddefd69ded1407640c23c3d979a441d83c86c68c07406464b0f55d3cc2c40 1 0 \\x000000010000000000800003af1bdfac39b742e1d5ecac8b26c8419836825496cc025797dc9e28f0c24df3cd7bbd023f0118044b1d1ec39465b449717e5487b315033b7f2096643c028c9beb703dc11e422f64775130e296e1cbab423664000441acc5356e967c6f4832cd02a42403601a21ceaded1da7eb49d64bacbcf6409e0f7a87b0f479a6358b14d9a1010001 \\x0c1d177a108f2c983307937ff561be048263caaa42ec46d0d4c738b9ea8c4519780bfbc84155ff7070881cc02a66aa38b3c80f36dbc28e1c23b1cca2b489a30d 1657096407000000 1657701207000000 1720773207000000 1815381207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-261 \\x5b86f7e532533caf0aedb91655ec1f74669a13bfc4675009d53d678af9e554f58cefebe360b77875f76b7177ae4e38f5a562b797b97637898c80bc062d7b6fa1 1 0 \\x000000010000000000800003eb4e52d3352eabf3495322f92b034e1a192d453713fd8a45b5c2d8a144329a5a4ecbb049e0f88cc58eb03a38e23fc56c25045473109411564d0617179e8acb657aac7f57a0ff5a620a53933ba18d54b6416a6eafef0c2d3d73b2d4ab0ec1c05e041717c7dcf051b9846dcc1e69614234fa40e10a100e0eee449849d253da7599010001 \\x41e057867605efd660472ccc4b8bc7f5098f31c39d3e61dc0aa06df0e63d960dab85f3a9b078f306f55e19f47e57bb7c8f94e09f5a36c2d88cdc93408198530d 1669790907000000 1670395707000000 1733467707000000 1828075707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-262 \\x5d8aea955c160f9c852eff39e1e7a659aafae2fde300dee6e77b6fad6987316246f411f4c8524b0a02f9722855abfc60d823ed10a34bcba6dedabf0c51f98e2a 1 0 \\x000000010000000000800003a0aac1c5dd73fa56f23a5076aa553c70591ab2b38ab347b29031e40ae3f3d72b527401ab63c36d9e143e40bbe18cd1b4bc910a37ef539d5b3faaa392e805fa2ee1625f32a3918589b10a431b26f2251e4a1d07b9ac8acf509362b2cf2006182c7d89b01ab677f2ec3c5a560ed58599f665037d6cf037413fd6cd5d9bfd78a2e3010001 \\xc9b9b6b1b9a09692d0cb0adc9dee6cfb9efd0db8db25c11a5c3df67e3faaef579fe215e5056f972c91df3886ae3d3cbd2ac96897d4e38964ded623cf1128ed09 1643192907000000 1643797707000000 1706869707000000 1801477707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-263 \\x5f5a4bbcb5219cbacecdba06c62e4e89b579484fbf659935f4d46d4bf26a4cff3b1ebcaf57baaaf9c3c2f3a7111d7b6ae7569d76ff5ded2c658bcd08d46adcad 1 0 \\x000000010000000000800003c04f4aef82a581ced14476f714bbdd48e2a9bd18b4601ea3c5ee3bf1c53fe59a3f3c5169599e3c062c6317aee441b4868f33318eec4a1ad124ddbec2a90d405340a4241e19e51c2004ef2a2083b1ebf28125d1f0ea663a9a0b85a6a4d5ecb27e1878cb9fc9fb05a7ad8e5e2936e119069b5375fe5ff6c7a76ce358fd4d7c6f59010001 \\xd4ea8285ca28e45d9f008abc22428d80052f54b2bd80cbe02e65b839a2c7df6e099a8125a05d1fc4f3fc951237b405138ab55e472de09d2dec4290ce3ac4590d 1649842407000000 1650447207000000 1713519207000000 1808127207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-264 \\x62de7888218737e4189b683860299ca6ed6e375804174514ed9c41a657260d73611e69dbeb34a3eb46bc079605d859e61385297133ccc2f109e3e0b64bdfab46 1 0 \\x000000010000000000800003d6fcbac531501818c86c8150e3bd5a125fff695307fec8a0afa4304251ef986288c08b0fbae029d0da6e80c9eef779f201e7fdafb37a726723c331b4d07b06fcf091df631e6ea9b055bcb63d748bca448b20953cd5bced1ba1c8e64fbe748c91031d497a65af26bab00becbb9b5fd2a21d6506106176d420aaf534423b4bbeff010001 \\xcffa583ebf7b337ba1958b80a267020c1a2900b7f3b54389da452bdfbe1c7eeb7051ed57e4c4b39b89acb51f3d68a76d00d76b3aa753663f4d212867d75bfc08 1668581907000000 1669186707000000 1732258707000000 1826866707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-265 \\x63fa503eec90c941070e704d06b70dd9cdbae14fbeb56415ca93c99cc132517235e98120e8932f4e3ee879c5f18ca119dbafe2d258ecbd5ab96d66433edbd3ae 1 0 \\x000000010000000000800003f938f484d39cb47a9102508073e247ebd4723afc3938faf76150571d5e0fed933138213bb2be06fdbc7e352f79e954b56aaa5b0d0c2e09f293a65fd820d3e1b6324d99c3fcef6fa27dee4684e43b63e90f50ff8967f728fb02d0e0ba1c7b4915d4f39d042c0fa5116d78a11d2c6a7ba8db4e0d3a363cd53bc708551afcb482ab010001 \\x5b8e60fd450375a23e9d2a068255ffacdf9f8c5b0fd1e5f07779de34653204a816b76426ee6f3d79c0b9cec93cc6932f659c11a7bc9bed9a648e80daf8d8750e 1659514407000000 1660119207000000 1723191207000000 1817799207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-266 \\x64c6225174aa193d51db51b7736f7c732ba73d7185ee281606208926214918f330400aa2c4764979f7c8e8b28ebee834a2fcfded15a76c85cbc742dde1d4309d 1 0 \\x000000010000000000800003ab6d5bac548749e434004a578a4062c9b1ff891209dcce1841a33c1840caae9e8ac9a0d3632e90585378f80c5292c7f506b54ae90f572a3e25448e8602379ce8adcc97a9f234cc03a67032b75ddf7299a99ca4004b28f1776ff4ef4fd8a5e347c71b786f7c605b5e957b1770e8b1a4506e71b109317a3a9335a023e2a5feda8b010001 \\xd0dc6650e7825f229ae66b1be824e8f334b1ee8a51f4aea06674d22e2198a78ca62772e5093a86103031d398406ea4236eea241cd337eec259444c5c89dc5e08 1664350407000000 1664955207000000 1728027207000000 1822635207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-267 \\x68aaa88de2a93ea8e44d4610910e1913d6d97c7c0244877f6a74c7da940ee87f1e354caaeed0d7c0788a9e6773412810dd069fde46f56e8984286adad9bf7bf5 1 0 \\x000000010000000000800003bc77bbaf57365a7088738f6c4fdc406dac62ffe55e6851f5446eda8e6255b5b0793b61675557800c544bbf48b92571d533b35f9e66fa969f4ccdccae0a451d578a93444e6e4940a0775d0db3542979726703155c340b1a577dd302640c9c2358a16e85e9655ef83e6505b34135a1fdec85924ed851bf1407102268d774e32bd3010001 \\x954260cc74ca392e4220195cb7b37ecd97d39f85fb9f72cd4af340cdb0b39a286d946016be3f29286104dc9272c13bbf32f920a6ed6233d0689364742ff07207 1653469407000000 1654074207000000 1717146207000000 1811754207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-268 \\x6b9e060cd1c14bcc38d622bdc5643bc16eed6daf751f3aa185ee42b4ae856e265ec7c303123aba5925b78575f9ddd18ba95445fd79c711576ed69898f828dfd5 1 0 \\x000000010000000000800003da5caad5151037c30281171ee06d1c8243b8e6e4ff9b6f75924426c4616c164eb56fce77b5b294d76a5a642810d0089df544a54cc9e4355d0b3c1af4a370512a24e8299aa1dd47fecf6568ac9d7e023c83a033c0a87d541ae966fdce8733f361f0622b631af8aebec25e18003fd23c2ce18e96c1695e802e874661c2b603a877010001 \\x6e57874c315cbdddc2cfaeb1d79b554407e0bbb829a503784dc24746c196db4c043b048619678d79bd89f1748f83882a2a5c98f66e66db630db6ca182b3b380b 1638356907000000 1638961707000000 1702033707000000 1796641707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-269 \\x6de26f257a500deb565c84d3a29d1d999f11ba8ad1981c1e1298504a5d2b6041a20caa43105d7b3683b9e6b9e7923b3236165c37b43a4e36da007880cea7e3bb 1 0 \\x000000010000000000800003a1635f7dfb468909d128305ecaecd734de2120d09cd6cb97889e2f290428b23562a9e8576f7c965b1f84da1f1889682075e71914703bbd47eed5668b46e7165fc1533646516901e982838f12a6d3c5e5a0a94de42511481a6378769d294047806a480e53d25cf229ba52e0422d30f660a289c951134bb6afa27f27e8acaf79ff010001 \\x8f759f32fb307855d152f109b6cedc584b03d31b85725bee55773213c1b27c142aa9e2fcdbb8595b581a46dd86d4bf41a84de3948b766c16789f136a4c8dd10f 1648633407000000 1649238207000000 1712310207000000 1806918207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-270 \\x6e16b37a03790ba720950b180670abab5f483cd6cddb3297270cfe7a19277d830889c81bf4d52e48295834c7fa50df52f5b154efb897b734b419d242cb54eccb 1 0 \\x000000010000000000800003eb5f374324930a2651ef467e06d19141328bc628d4ef6e86a589958b1ddfd9bfe06f07680928da32e0b545ed4dd6a1973065fcb14787673e478ea593926afd95036d77a90c99a27910756fe8eeb812e969deea429fc4d91ba2a429c721c19dc2352684115a9e76d71b15d3f75a863b0716de4a34f497d948424b75ea41703ee3010001 \\xf5200d57faf8722f5fff3f99cb9e387d0cc2c704e162234590d6ec391ee3b8643fd820a996c63a6920118055360a11d2198eed2fb2342dc17061edc6100da80b 1657096407000000 1657701207000000 1720773207000000 1815381207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-271 \\x75329549658318827e474098b2fd83668e057e32adb5d8aee5020a0d663607da61acb7734f10eb85d62cf573d229281b7a5e8772ffad2ab5b55b71c380b4693c 1 0 \\x000000010000000000800003cbd477af6ecb4646e81fc53df6b55b9d3ab2d408ffe52ad3f3ec9692153136ad22c9e59ac2b4758377857d8f0cfe467706171a59078682d98550fb6b9078391f09b4b9a34c6f66dc13707af4e1f9fdaed9ef262538fae293bdd31557b7f1c0a7aaf48e25781836fd98da0f3b8acd9dfcec52bde457e1f676c046ede9d88df37f010001 \\xb2dabc2488ba5941b0baf1301016bdef385f2b70123c4e089ec9463f46d1f8500ba8c052dde527e5098249814e0ef2817b55afdb1128a26e4064bdfcdebfdc0b 1659514407000000 1660119207000000 1723191207000000 1817799207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-272 \\x751ae75e268d34cd63ea39d9ef20cf518b38997bc31bfa55e087707ac477c615e1c294f8483505c8cf16ff364f30904259b42c95d0aa816e01b5669088d70f81 1 0 \\x0000000100000000008000039d41cb2d27543079cd2ee4a33ebccaad28983033f7ee3765c98199b33b48367f9d0def3c14d832053e95d42dfaa8cc8608f860b3d3519eb14a30bc4da23cf24449e78277060f85a373709ab008920ab619cfb66642dd9ceee088f0ff474eaa915aea88061610084b502cebd18a6cb4493f61cc04ed3b215747fc2e54a15474a7010001 \\x5135b6654197dad510a8b050a2bd8495af3eed5f7fd686fd1c6a0527f28915033febc0412932f02834e5b46990b5d012828f431d203daa6b16dfcc3b72a90f09 1661932407000000 1662537207000000 1725609207000000 1820217207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-273 \\x76d258f652d868972fef5de4b530560c5c70ccbba8e3e7caec50f9372d8b259ab0234c66141dcf2e06ddcc2dad2997465896ce9f3f382c7ca29fafd3ffd1b416 1 0 \\x000000010000000000800003af468a55c177b26dc58e442d4c4a961c3c38c28d111484f86289c3b43d2ad1d6692fabf07b1a421335d243cdbb8fe58f2147117e9dbd0ac814c18396ea84224727074d7eadb5f7e780b0ac89cf96308631beed4acba9ac3d56ee99292069556b053aaad9d8415bbd9af2e4e1ef7971625cecde2aa2b52365184d417aa63d87bd010001 \\x4a576ee1f477fc50974daf02a009f3da1969f7ac289f6de75d2999d73a7c0dc779096f0c213cb4ff1bde7bcb5ee644b534b924b510af20d63ab7a42841336b0c 1658305407000000 1658910207000000 1721982207000000 1816590207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-274 \\x781626270d02461a773574a5afa829f18c62fd6811208ef731d317284eaf46722b0ee8f1bc10b2576a8bf24a25cabbc5d6c99b7170aee33ce086aec1f52fd014 1 0 \\x000000010000000000800003bd3ef0d94e8b8171137850d0de146d03d82c85fc969f29e4a388b65350c157ac8fb6f266f694f05b3f80dba0d5a5bead80109402e3b0699976d9e80add0ecd977447c417b5ddc7c818a4d796d38d1bcb76b4dfb526ad13b6b28b4a02c330472cadf1cb717c9fc80d4649792ad46495502c28ae5190b0ea9d9624c8fc8b67cb1d010001 \\x0fb81ed9ae8e1a3bc972768eef43d159e16fabbbf3510306e963342aeac8b9efa51a7d858b81a81ae3d74656a89dcad8b4310955256b75fa4585110167e2a80c 1650446907000000 1651051707000000 1714123707000000 1808731707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-275 \\x7b4e5ce066310dc2e649e34144eaa3baabdc68223c6bc489776204f34b0c67d2c4e638408693f837f4dcfc81f1ef3fe7180c969a601b57932e18fbd0865b1f40 1 0 \\x000000010000000000800003c5703c892d477e2d6d8989f35705534cf71ab738dc6e158320b877a0945e6a671cde262520d2916aa0095861378e6c35fc64ac6d6c15f970543adb79581f877b21f4fac8a0d9adc324d37d31fe8308a685d98e4eefd95b5d55282be9197075652b2a6134795470a4652e009209a07fbc8563d6ab79a965909e0731bc58bf222d010001 \\x884072a4884a40664e120fa4c40fcfa91d8111539c16e310df380ecc2a933609e557583cd033caed5796089fb81629a81db33b9aa78dafbb3135e9359b222609 1667372907000000 1667977707000000 1731049707000000 1825657707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-276 \\x7b56574fd157a252f7b3bcbf755d84ba675788335a475cba07f95d367a9fcbb00c596846e13544768f759bea696e25740b0a75fe3e1a13b7a249d731a4baa4f5 1 0 \\x000000010000000000800003c84c740a27f0ebd9f7318a665a1dc88b19a54e655f6263cc37b1961e2bdb3d07a0a6a8b084fce2288e74e94da08dd92368a04ded7d7af670c9e62fad940de3f7efd0e9650d374d7257ade8bb53c21a38162d13380c5b37817386319f53cbe5d5c1b51e218a90d736a3e7e0e7ff8053fa3059fd9807d92eff2d1d3ede1a44471d010001 \\x042970544bc20e42e2009c6cb0f24fb9928f00f94982458e73dbf342c86c5ce98d7a2f231cf3e3864d37da28af40cc6af5c37f2c2381be51cafeb1905e86dc09 1667372907000000 1667977707000000 1731049707000000 1825657707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-277 \\x7f52f35a97ddba3d5a641bc53f0ff6ddf8b0cd10dd43e4f12d8bf20df229e4cf01d392fd7b0f51dc7f054f9798d589e0a5d342d3981af42d914f3fcf7438fbcf 1 0 \\x000000010000000000800003d29a02098e055250021ae38351cacc9d0cd3646193967390340ff096d5c8346229ce82ae95788527b952d347d16e69d08a1db2b7fa4c0f454e303c28e35e104e1a643a7c319d9732e2d9e9ba7bda488c4fb4e7be49d6b5bac72b1b0ab14956858a5396e7d269014cfc85da79b0054d87289d2cc16001a0ff38d7499cc9ecd65b010001 \\x4624f5ad5bd06e59ace7ec2b9f0e57f14deac267b4a7caa5f488ddce0083f2285e9353554ac191eb934560ad4afb99671c674b86154e6a875adb048a6d704c0e 1643797407000000 1644402207000000 1707474207000000 1802082207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-278 \\x802636136b909fdd05bdde9e17d591c7efe93a327f90d8c8670019103473c9ae225e17ac715ecfabea48a3c51f55deb1d7db05cc6475c6c7f0f20cf0225de9c8 1 0 \\x000000010000000000800003d202dde1c8eec1a3b31e2569297620947cd8e6b8bfa144ef9ba2924c7989877d5a02e54bd6db2ab45d1ac2dabeeb6db6dc61e27839ceea1596d799e70ad7f9b445ba51bf605b2e324cae88ce8cc4b935547c640eb9bcddcacd2d3230e414510ce52fad845c112bad6db51c643966ff7c91e71871a70bc705d412d4f92dc04ec7010001 \\xee9bc3bf7dcd6e2ebd5d7155264b4e1f658e4f81668fcff017de27f5b650e2acbd4df1d261bd5c226f8fb690419d1aa252891fa7829b56735356ec65c2f61703 1661327907000000 1661932707000000 1725004707000000 1819612707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-279 \\x846686ae6dc00a82c3e46e1947da63f32cc78d6bfdaa7c24c7691414a38a6dd036b26e96816979d2ea472052ab54d3fc7e577d880a5c40d81e4e647dc0b242b8 1 0 \\x000000010000000000800003c27732175dba75748a7911b3570582d414518b93bd17b6b862d92fdd7ead41a5754f477d02b24cf22d66c579fd30e06b776a79ce3272e93ca376fc0c2e474b7bc99eb68ae05330da42e0b14aa891e6bb8ef269c06dc029abc05ada76d4a9b193c3a602e596c5b707f605deea69eadc4e1477019e1860c791f6492c7e8fedc909010001 \\xa565f43436f33481fd040c4533a513172ea581b1adcb23c590c6cc8ea6a257042845b4a081938506399262fd450cb6cba1fc3efc5eb1829c0e2237d5e332a50b 1656491907000000 1657096707000000 1720168707000000 1814776707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-280 \\x852e63d61a1da7042f9349987a725e67092a29b14a89cdf06e3db865f925d020ac70ea6bf4dce6a411b10face7cf544af22fd60f4480b97ef48f9588fd511952 1 0 \\x000000010000000000800003c7b26fd4e6d767c7afd6b9da077c3ed99b97bf77fe6adb625b0b41e069e721aa0b6f74b7dd8b7b22e010183ed84a6ab34a0dd31534f305615789652f3fde62ece2a33c5e5bf40cb1e7f4dc8a4886b0552eeba5dc910d71bbf653c2f2c9de72bc2500d2ff78ea5c5445ccfdc436b1d48cd58e80653ad2484b1b4372581279edc9010001 \\x2db7ee95c9c0aea8683decb46917bb24b0ed275f9773be612ce45d267e1c74f721b70cc6da01726bbadfd597d4f4fe1883b63ac11c7ad7b932fdd1cfe34c5802 1654678407000000 1655283207000000 1718355207000000 1812963207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-281 \\x858efb3cf46740dac0a1f4a268b35bafe95ad61ca5fde745b7a006aedc190d853ff3479ecc8b683ffc30004a46a46ebf27b0b729cea22d9e6b152e2ec5f2c283 1 0 \\x000000010000000000800003c59aa71f0ebb840f2afee250bde633a658d1043b9ec5aa5ba32085da48eb52033e41af9f00352c1c367c0c8b23eb89c7d06457f960513a65864fb59c494b2ba7f98646b7b4ff6d6d9297d28d47e0abdab5b20cbbc2cfbd99f7b4216469699697438df8f9773f57895cdf4990816c8a86a3cea2e7fb3e195ad7141add56780323010001 \\xbbbc4ac24eee9ab4079e3d0a00d21e06e3ad9850e495a9b23087c5eae55db9c9be4fa21ae8990d2f83b633f2bd21d9979b75b4fad63611f78a2d260fefb8e70f 1664350407000000 1664955207000000 1728027207000000 1822635207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-282 \\x860ea0031ebb0629c85f9df60f60a7b2cb276a24b6c6e792afdfcd5e329eb9e66d8162ce351c869ffc795087650c0bea9d98ebf237b58f3a35e7739c96dd4ba4 1 0 \\x000000010000000000800003ccbd4e55b81e8ef79fc229420090aa2a4fa2a7133250bb56674cc80f842aca5994baf42297dbe848af3572799aac2db795cb087a28f69d26066b53cac06e4028beb7f7e958fe84c313525645be2b6e157799e201d7dc04df8af95b77767ff23c33735610bc5c6ca89ec891e1d675ed8d10f28236f896d878bbd172cb9b9cffbd010001 \\x099c77100b2b892b72c4c9e08275dc6afa4e8dc8dd64e7b5433b60a5fd66e58dc8d5ab108d88abbdcb63da13f6c77372b9abac148cc5d51145236498c048a508 1654678407000000 1655283207000000 1718355207000000 1812963207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-283 \\x87ca7d03beed00a1496c0a04c9e2b02a234cc8d2fa23f7d5650cbd630e2d0526a72d45a2e2c60c5486ae2a94365dd15f4489301f0eb4e8c6071c67b8ac417aa1 1 0 \\x000000010000000000800003beac4553f39358ad875c33351e67fc2eff5e5e64e31f77258af111b4bb3e0362f4c00b8dd7f663c9b5af1bd193a20b3db340805239653d2fe5a3c61bd1e6b8939e26e14560717eeb4ff651d6911555016c03c5386217878884af92f8ff1ee83bac5c3f0a495e13c1faaefddd393058b68bd7ee1eb142494c0e8d4c670f63c871010001 \\x706e8ddc5bdca867f125a125e1c4158d92f91c59c94f8048dd7d7cedac8a8deba9b546ba12023960b90ade3b21f6930fe984ca303209978afe5e94c5168f1d06 1659514407000000 1660119207000000 1723191207000000 1817799207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-284 \\x8916454d1c3cedae4fc6dea96c002c9cfb12328fe718d91e772f3c4483bfa2c1b4dcf47ae72a8e70a825f186ddffb76f7b15bba737268ff6a500453908fd8ab9 1 0 \\x000000010000000000800003dffbf1b98343567ccf17fe1d960d26ef802e0a9076a137a2e5c80995b5d1936730a0c163bf0b410ff168c4e1dd9220b56e5b5fd22bffccab89d20c10df63be283f7c582ce5bccce6620c05a17f5e714a3a487440ccef2e33e4c561c7055ffaeb2230952f0b0c7774a66d0c2dc81c67269c40b1accf0d950d4be70fc36c4d239d010001 \\x21090e46d34102136e7fbff12918a3449b2e927c39d88068715216198fbeb06f1780540105f80db390f6367b96700d9f892b1f96170880ca6a1af459305b420b 1663141407000000 1663746207000000 1726818207000000 1821426207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-285 \\x8b2692d68303279e1617e883ad4c02e64690be5ef76d511ccf18894fdc6fe250ae64f722b319ecfc867fb8e5943fea75857542ec66c82b8e6218ed5a716c5b99 1 0 \\x00000001000000000080000398412ff57d5a6652f17501a848dbb55fd7f19d50b078b7ac7180462a1e23e91fe4b30be8aeaa05fa526239fc7273de282f98b32603034f514180736a64ad31e5cc78db9f5e940da235f608d1f37327c0614502c9891b450a614517b078a80f80e5d7cf7ecab6eab7f814db16fa40291f68aa652cbff17cf2a9cf4a61d2e1edcf010001 \\x6c714305107588b335685d860fc471bfd8017492c28a15ea87d569929a4285161fb3de27a045620371492796e4b85232ecf732004c799fea2eb781ca5e7b7307 1646819907000000 1647424707000000 1710496707000000 1805104707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-286 \\x8c160e85c1eb38d824177079910b6c33e50d95999209a28eae5f1a8baaaa750738a5f207c8a5b53d9880321504859faf62ec509fa208ac1a24bba7c3a2912a52 1 0 \\x000000010000000000800003d68c84dde91d1e9871ba839f7ef5ae0b93843bb6d4b92d32194d7386f9fbc1fffd257b9a2381f18f301c35c0573d6873ed7cf144077372ddd98c57766b7c56fb6fae7a2409a6aadc84fe4593f3ce869740826e2e26ae97b2d259d8510478f5d301571d3547133107faeee671f8754422c68ee310e30dbd1046a2e2ca820dc9ff010001 \\x636b39a1ce9b61a3a6270841cf534cc4164aed76a13de85b429921f84d1a494318504b08ca6fd120c4ad89e0608a03ac6b3e6702a0c50a4fe7169b79d79b3d02 1655282907000000 1655887707000000 1718959707000000 1813567707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-287 \\x8d320397fded1709e5e01a8f7ac01dbe181140a0f43dc3fc01dd7188626e62eb62444d2c71fb25a577b720bb1952f1bd0444d6b4af38e4c54018a162f1dec658 1 0 \\x000000010000000000800003d4d8cc4622469e48fc90fe8f7c204868fa7b3750216f124d276309efdb6dbe7f64dbd6a63dc4d962a4a4f45e18e55dfeb8247197c503fb278e70da6fd64bcf51c9ae4b9a3e879418bb14a97578cb4e2e47ef47f0e119f45f74e64b61247bb6e1c04285bb9b0d291f39c91ec997c5ccab21c888c6a6244aabe290950bf29162f1010001 \\xca24336248736c185169648d9f444198539d161cf22b4ba856233b92bf67b9caca386ff61a7c339af91e4f5ae4eff6523e5b07672d3e606180bd6591ba4d480a 1641983907000000 1642588707000000 1705660707000000 1800268707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-288 \\x908e3d2331b3bb18b35501b6665425e3e45426d59109c385bdac76ef715fa3fbaa3af0c4ebcedd2a04b0e907a9ebf9158eda2e911e34e1530ce5d5e0688f1b24 1 0 \\x000000010000000000800003b5ea0755fa2457a349a7eb1898708c7a603a64b0ae1c25e81dca75ab5a579898ddbc10520cfa78188a7c9bef4421ec796cd4ab664e390c8fde6e77a9f35353749c11e7ed4fbba1b789fe55cade02d5df8ba6b901df748f5bda0d08dde9a3246785bf394995fab407e7c2df787f179c7bc35ce65681a6751f5b412686c7d77c49010001 \\xd2baba2014a9c8e3e72e3b9b88cbb763b825d8c465d3f82cd1a3ecc7e885c0cd95517dfd3151bfde73d121fda0092d7acc01894d4a24b958a65fe59077577f05 1660118907000000 1660723707000000 1723795707000000 1818403707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-289 \\x952219e9134c5f5a8f87fe0f1ea754ab28280a39011f1d9b2a04008906bccd3270260d7bb923e656dc3b00d33a606e24da72f24ceac12ee6b7ed1f31d4fb4023 1 0 \\x000000010000000000800003c3e73870605e95b7e135a20089babfb54bda77b93c4a1cc42f5527326d51badcbb8849304c700b377e42ac394db60f3e49b97db786599dc7d2a42b022695ed7ccaa9cb89005aa3d000f4e5dbda4385dc535bf0835040d16bba8b954deec94860fa66038fa82ae32356482d3fd1f0aceb290d9b05f72df07336d86775c71d887d010001 \\xe42ac66ab7c8ddf45a726d9b5bc6f131aafa2e3ec46f68af6f5fb9a4945c322a929ad3f2a8212f93e0ece561e76086eb7058c5c4bdded57dac049b4919fb1006 1641379407000000 1641984207000000 1705056207000000 1799664207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-290 \\x979eb71d6bd24e30cf65ee5736317865cc81c9fb2a19155951ccba2a6f7bff3f3440c8dfc5df7451f9c7e38e5e2449680abebe7ceceafee0bdabffe903def43e 1 0 \\x000000010000000000800003ec6d703c4a7487dc153d2252a23e367eb8c4354e8cf7841fecc5c36a2eac6648ad13f1635d5df84ec7032a7d06ccb012aa83c4f7ffcbc4f8dcd457d269cd6d77aa8cad2446be1d724fe61f4719bf63973d660b25e3e4bb6bfa6581e71c15acbc51475663b8a0ea3bf8a82d4382dfa5ada405a81ad0d697ddccf01ad4d11f241f010001 \\x6ef4aae8e7df5f4bddf226e0cfd4275134a3a4d6c4f983497f87063d08d0966650294efee3889049308599f1b62c79957ba71e2ecc1d3d8ff1ec5a34e938da06 1654073907000000 1654678707000000 1717750707000000 1812358707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-291 \\x9a929d4069ffe77be2334a11a2636be5eb5a294c1dd2593d5f954167a2d4af77c731fc58f9f10a2f6e71a6f879737ba3759b0f4acf3ad21ea0a9c5c7cec9fb40 1 0 \\x000000010000000000800003fca58e23b5e5272aba5c5a7e2f1dfcaf81aad9193c79fe50ee2cd48a06738af5f5c0a7768a2d775602419ff27652cb9f6196e518da9e85793519c4a40fc8ec45ea93905ce7fe7300aa8264661f60f96ed9187e8961bc9f850d93af5e0b00ecd3dc26571ee4fd9dd6737ab329876eea8c3676adaaa062f9ecb2c4541a9b7b210b010001 \\x456a7e65b54baedaa895b7838297eb2ac3e88cf25b22e23bd71409358aff71ed7786ea2e403b1127228da036e4bbe4078c45ef23de1b48d8641c74e6b66d3603 1657096407000000 1657701207000000 1720773207000000 1815381207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-292 \\x9b764ae50d05b10007961d5f0df545bcb5f326ef245aa7b7cb015b88a07c76185c7b5a669979e4df3ebeb1c46f76ba19a1ecee7f498f5e97774062a4f658b3cb 1 0 \\x000000010000000000800003a021627ec974533f8fb88e8f40d59c024f81c95ea80a5cf9e38816d00b789206db41b5fa86be62f7ae8356b648a03b21100ab9c30aea932e816cc74a2a4fd44db3596e2a658e50aff0b35194339d61ec2f71aad1fcea205c8e2019b75b4cb0d358d17136afc21c7b3aff637ca149c52d42df51055a9e100fb20ea52c0039b835010001 \\xeb08b7b5480f7f5462c2b0ae557ca41933d6281bb2a63e6eae12ce7115974e51d7e251355a69b16674d6394524ce59076184180072c4ccf5459526deed8dcd0a 1641379407000000 1641984207000000 1705056207000000 1799664207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-293 \\x9b2205e2db384d02f28550d3506c3a96194e8a56318e23792522966591519051a3982ba958ed340cd5abf5c148f3870a9b4b960b0605e3df1192e473f8baf412 1 0 \\x000000010000000000800003c4288e0554713ad30711d87ef45f45222fb98db467bb369672f8ef42dca060d98c38b5c9942afd0ccde98a51eec8311e40b97aa96c004795faa913b414630f133a216608644c1ec5f944ef1be98b40b5cd18f7bd79dd3e757eebc569bd78cd6981f8570361d8b17d14175c17bbfdba4edd2f928122ef21addd34fac930ff6245010001 \\x1a46fbd5083745684c211af1e581bca65b5d30e54b626188edaea6e660c8b9f9d60926720313e95f0e98f161efc364e52f5822fba3eae4da1b33cee760a2e904 1666768407000000 1667373207000000 1730445207000000 1825053207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-294 \\x9d22954fc3104061aa1caabb09a2a73679e448c9f16fe96604421338fb6b8be64caf6bdec3f2a341a2b81d16c62d0d16a313dedf31f99918600801348dd34292 1 0 \\x000000010000000000800003b95ad0ca87c15f1efd9799ae5474fb89a98fc083075934344aea35817a8be44d5d2db70b9aa11163e27d8c78089b3e4e51324fe4cb8ee71f89a4cabcd5913d3d941a1f6f5f34da8c997b6bf1a0c2bb7e5547834d6422e52e34fcbf7456f8daba42319b3564fb9b9dbaa3549fd2b7c22a7610e5cfffc9d156c079ebae925764e9010001 \\x5f4fedf65afd3e4de4dae56bb864106af40a28abb9f34a7024a946ec317324a6c23a0996c0df4f576272f8398ee705965e59ca43e4bfab52ec8d1c40c2ce350e 1641983907000000 1642588707000000 1705660707000000 1800268707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-295 \\xa022b210850abba1d3b76113806381bb54855338c5f62107877042075d92623c777a9215fefaab764962be17af32c29bc4f3e2010d38bfda2620d942dcc96686 1 0 \\x000000010000000000800003c68eff8c38dc6d7dc6a7752daf5bf5c9bb0e014c49168c17dce61d0465104d7d0160a39cf1fef9dbf8d19bfecb5cf1569b373c7493318fb717912a1f6a35bea03ba8406599374a91c426b7d09e7cb9a0b0466c1bcd2e6304770f1f113c9918f1c3b97f5c6d8d360770570d75b6a70b4c2b024233219b52cc3235aeefbf50868f010001 \\x767f2162dbf003d8caf72898d06a8277e7786815b7e0b2c8a01a086e76bbdac6d05a52106312027db74338711f21ee5cc974377da9f5e99735d5a6695052f500 1641983907000000 1642588707000000 1705660707000000 1800268707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-296 \\xa16e43842fbb2e1c63e4d5ee28c03b6a43c17d89787121a41773650c5ef0ab328682c5ac5051842a3af87605dc3db4298a4f91b1d414774e50a378aa99e4c7b8 1 0 \\x000000010000000000800003a51b5f98e2004fa23e4a33e1261e8204f202dfa0294edaa9ee7199408fc4ef54bbfc83d2a0b4e80fc33fc5174380959677d0c5ca0dca54a1e656fe316859a779f89178fcd781ac04ca51d965a58d84679eb5302547762e0f4cec0382c578888e0fbfc3689fca50599f55e33bde3d4997517522a1e134959a3af7f1363e814607010001 \\x61899ded2e8a18cb0816d5b02b6dbd43652119a207885c1d4656fea528fc44fef71050a322795daa1699317526a9dfadf86e22b208789d3d75b80e06acc73000 1645006407000000 1645611207000000 1708683207000000 1803291207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-297 \\xa2c6170a13d4bdd2b33b261a2589afcff909ca35ef1d286fc174046bbd4ef50efcf0aaf7e11fdaffc76520ac3429e863567443251a3df5358ce47780059251ac 1 0 \\x000000010000000000800003b3c0da469b0bc96f64b1f0ecdf37677ccbd94f11b7f3a182a802445d0bfcb1ad6ec7a27a2d7752b45a40c73c190514ec5ce95748adfd219ea1cffbc795c60384accb1f5623b0bb0d3fd11ce628fa5640d169669fef4eb04f46c6132706eac21717804f34e5e2cb61085365ce48c96b1ae3f51036c7ad0fb527533f352c1f8ac5010001 \\x93c79c5a545ad37c5d80f69d311a4eb1ad5ad409ab2597ea19ccf8d829a359992e7c93a0d41854ba678ac65b541c01d2c96a23232ec43d29dc1e5636bfcb6f0a 1663745907000000 1664350707000000 1727422707000000 1822030707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-298 \\xa3be61b60a777b81d434f388f84f281d1fbacd8fe044fc36cff7ed4a4c601289fa0186a00d2ce05a08dc52efcf22453db8c8519feeb4e8d7b49be51e4626a27c 1 0 \\x000000010000000000800003d3f2af00f1f8cbd3eccfc078f31d865fc7b234fae6ca1b9f6936ed66b4935986a459ecf9f0ef5454338f22acbfa5f86033c131ed73a1794096af1cda0548575b07d1ed7992a7fb04a95e35dce9d367464431ac6b10234caa933405feaa41fb94e4722d574f73cf0d0ab0f9a5d0307f86c47c01871420344e920d68ec7e687049010001 \\xf57aba962f5f5f02444b4d6704e8a3d4db794aab5b149f1bea61fab49c347377001e9ad559a959ab89af0fb82dd5be2633878b10415e1611c2eba7b38ff87007 1646819907000000 1647424707000000 1710496707000000 1805104707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-299 \\xa5e2841f476f3e97ca8b45e959d0e7312d88da504d0cdf47e731d216bb1302dd370e0231bb96175e57d2a8af2f3d58ce944e269fb7119273b7599c02c8d4b24f 1 0 \\x000000010000000000800003b3a4f02da25eaa9ea7736cb014096eb223e9c259a73e716c712d09b2b82409683563c0d50f3aa733dd8be583402a449a9b9783cbc4a7355c2dcfc5d5c6ae3ce00c95c0c7a4238e949897013526f2f1d410643d1b96b76a3ba98e8e98641c3ce9de6dc1e9c7dea94ce73031299fe33f1f6d5109ac12bcd4134cde9c75e03ceb67010001 \\xa7a3eb7446d8320cd64291b668f1b12f7c76b3adf794895043dee0f81809309f46aad09c429e0ecb91c503793e8a928d5e577eee60d0d8c25912e73d2ecf220d 1666768407000000 1667373207000000 1730445207000000 1825053207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-300 \\xa7be193ebfe07faaa3aa7fd3806f82386101413e1a38ef6dc520bf5b0c631831a291148b7e6f9589bece7ab54a2aa6c3c1eda3b376798239ae4c643a8fa06629 1 0 \\x0000000100000000008000039ffee1c5c8614103c6d43bd51698080ec86dc8bb5d53dd550684965304c2e6313d69b31db5e74f072720f1deb01ef6dd6ccb4c6b34d393e392b810be622ea9b7c7aa3ff009cecc41e61b11c3e752764f0f4a1ac751125ee12f7bd80c1fbc3a3b5319a04024cc488f643217e74d14b402b54aea17b04001a831f58e8edc942cc3010001 \\x4d91eeddc8c48a96049fc14ed9c04a7b51d243be942d9bef0578e3fc647df58f0b79e076a7d65a98f4865dda046482f301f21db7a4c1ae4275f7defdfdd13f08 1643797407000000 1644402207000000 1707474207000000 1802082207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-301 \\xa82663324569f4e695737646b49f096ddbce80b11639b5cdfa90a7beca2798c89e98f8876c4d01c7d47da5106ec14bc74282ceffeb06fdf63ec1e7006d145c1a 1 0 \\x000000010000000000800003c0f07e97a797ceec713dfd1d38d40f123dbf82bc7c2de7197e395c7b194b2ac1ea1e751f57a65806eb87cc1493dd4d51f57e5e2202f317d148f3fdc690187d084f88f7151d6bb6ed273049dbf056734cef0acc6902a41b924dad0e58b56d7251c8306434c4c07448b4713794948d5d974bc749de15cb9da865ee17dd3db296d1010001 \\x252e195f8f06054ac438ae20cd05f9e0ffdbead96081375767e0d6baf329dbe68e9059cee752783fbd87730ed452f438fc2d99aff4b490b15f9806158e341905 1651655907000000 1652260707000000 1715332707000000 1809940707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-302 \\xabaaa3d90610973f03d08f044aa377791642eac7cd8252d3f27c49147b156b581a732134528acbb3c2938eb8ecfbe438e5644172e62ea7837aca07b67c7377f2 1 0 \\x000000010000000000800003d795c2b021c6d6e33a1fae3e015a3c29c24a59e2212c5479648cc1fa467e50d2f49b3554844c1c5c0121baa785b52293a61217d136ebf7efa59ca915d871f32ad05fd4ee6f9d36dc908ca49d166dee23a97201c5a5f0658c51be9d5515abc9b695d642511f3e914493dcd8bd4e4daa0b7fc37d3d6bf957a072e853b9f369c717010001 \\x64a1a1c64cdf103a9fb6c7fcfb24ea75e348096d9fdc3a6e64606bf0ee8cf74eb476c06be8bb8a19c42b8033ca4a685f40c6c2c964010140dc3b14ceed1df705 1643797407000000 1644402207000000 1707474207000000 1802082207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-303 \\xab86b5314f48d4093d0d0e18d4919df1227ae2cefd60f1ad3b8e8e8b96494c36ff253911425bece27f696880921c848426b265aebbc155761bd1b1c7c9001876 1 0 \\x000000010000000000800003ce1f6a1d65cc5eda56798c8da166ad382dc85dac9864ea0b599839f16c4ab64d0f6ca3e4f8291f7e5af27617e8326edfdd3e00d6bf5abc230738783f7f460efdcdfe54cbb4fbd4aa0e340e9719113c08cc0d6c6b2614e4d5d2be5ec6f53ddda9b3423bf4e2da8eca8c7c2418f8bfcc825a58e445f7bd85134c1874a5913a2ed9010001 \\xbf7c510d57a9d62eadbcdb8d2b050e2543c68f7f6cd5cf391aa6f66942273ba7813dce0211978283874c58aae05907e6a27d40216be01a05a59a7b3801581b02 1646819907000000 1647424707000000 1710496707000000 1805104707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-304 \\xaed66316c2884faeb668a738d6548656c48a9c27a4389737d5baa892a92238cbea0b2ba7ef58ab28786d3531021fb50ab33fbb90ecca96de3793c267c113bfed 1 0 \\x000000010000000000800003bd73445aaecdf7c30f8c70637bccdf9619a1b8e5869ba7a6b175d61cc0e9877f60a226dafc52062188e2a50de85345a3d68d7d70387013b773d2c52f19970a773447dc8a416e16a261ee86359f0ea9a292437393ed25885a6e9714ab9f837d6c8265b8edeae8841f0c4c4e61027c11837a3d8ab1733288db9d669aed26dfce8d010001 \\xfdad83f9335b7f27dea3ec67ff8aaa07afd706f1db2a78e44b092c1d7b0cbe251da1d0ab8c8fc50507132bb7639b1c65c3e29bef1a8ab501b7051a9cfaea1d0d 1638961407000000 1639566207000000 1702638207000000 1797246207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-305 \\xb01afd642f205936fda19d5f85c025c7d653a74120868e7569604bbb18015a37e584e894bcdb3d26a15718c3384e18fa0d5d0cf4c06c6f888e68bb44b5aa78af 1 0 \\x000000010000000000800003b96f1b5b2002398ecf8d1d37b3aa1c50a5850ff4d04d8456dc63a6d3dcaddf8c8819533517e2c5846a43c7a4610b86f31cf6398b3f20e4f955e1972604e347164c55cbeaf96f42e874015630d38b471c879429bf698ad86a8e6d85a119aff230650be0507f6b209933a49764b99c80e5a8b49ecc78494d293e4de93a40872721010001 \\xd31fc610c723fbc1350e3f458321ccb1ad7db313181294cadcdd666a49fbc0d5b74599249dcdff6061494250fa03c930220455112506ada20f752f2bafde140a 1658909907000000 1659514707000000 1722586707000000 1817194707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-306 \\xb6225e46442f6f48d0fb16abbc60401811ea8078a7128e8dadfe3f0a2adabc51e409f5b0402194d94ceb4e553b780894db3c6495b2283c8d2b9402aa80d8d40e 1 0 \\x000000010000000000800003c9237c6379e9f3cbb5b19753a91543d68c447bdbf58ea2557f7635a72e03f974acbc9a114b18dd2d2006621b645450c1e1f1e77922ea5735499b9facaad18b5ab863ab8f8ef01dca28098bf5b6abe101317cb7d364b370358d76ea28862d479ee8773ea5adc020b0cdb4e1f77d2689927ad1e41653b2b5a3d8ee31c0876cea71010001 \\x69d44bec859250dbe5c0aa7ad6e6187ed011730d93efbf6de87bc17f81696d0742b7b2d45b892db7e8fc081e470c21ff8444783f0be865361c9ccebad5edcc0b 1656491907000000 1657096707000000 1720168707000000 1814776707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-307 \\xb7ae799376ddc239663724559c80638d3a44d80698404182ce249db9ddb829a198fc573cda53c87e9bd4397c4e6a6a30fde176786ee2865614c7820b8f4f5484 1 0 \\x000000010000000000800003f9ba475c33c3729300014da1e0bacef37b899086fe6240185bbbf69080014aae42aace3b58ac892110b6071c2b786899b42da2e3e9d0f992e82c6c28d408f16f8281de88ba2bd2fe2f1354dec4ecfaa8323bcbb02f436cbaa8e23705cccc893396e15e439e484afb9ca7185c928424322faaafe8309935647617b14ff4ff94dd010001 \\x26586cd234c77230dad1c67398c11a9256a71b22ff37066d8d38f61f69641969708e1cfc93248e0a02cee4d46f61b88b4b6d4a77bc0dab380f1529c5ee57650f 1663141407000000 1663746207000000 1726818207000000 1821426207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-308 \\xbfdea5a013ae3dc1227dc7112867f8ecf02a03455b8969ca327aa91e9f3aefb582e56ba11a1894bd04140d44fc2b7a2ba4bb1608c10cca768010f60859c2bb85 1 0 \\x000000010000000000800003f1fe4bf2dd1d9174656736d503525eb776b9f021bdc330224da3b4867a1b89bda9688a71fe57b6529c3694d272b450814f366992d0ef4c78798fefe7e2e9e6d360c05a410e1636ee6a5fe65a22004e713a6be7c8cac62e23cee9829107f0db2dd81687e04c8c6552abff23d93c1c5e48340f5a0649c9d2af628b830ce4dd0385010001 \\x0666dbd75077420191188271e03cfe703b0c18a71b2b4e858355b4c726ec4a85736950747565830ffc832f6c6eb5ecd340fe92e560594512973f1fc5fe854a04 1641379407000000 1641984207000000 1705056207000000 1799664207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-309 \\xc27a73aebd183ddd4ae35ad86c031a7c9bd47d89f604d0296b5f585a26782d0a8e5a6f70893e63ddce7ad8d44cf29093817bd7358f1898a8335f96f47b7200b4 1 0 \\x000000010000000000800003f1d8ef2298b3edec5213bc9911358b68027fa42d0ae8a44d42dde711583847d49e8f133452f73659129c6ddb25d24a595ab2c350e5eeb5255c56c69a238ba23c00cfd9b0759d60e860e38e20001d7b101924659d820b49f9c9683e7eb852a0706c6f67a06f0de00f4eece73ee615fd5d4ac0ae7c97df0c841d06b5ffbd43e6b5010001 \\x40ba8fefb1789bda9f8d26423fdd6ebd7474e2379d82fe656a3335bea4981c7685f569521f8b4e450921da52c822fc766217723bb73573f4d323916ddb06760f 1651655907000000 1652260707000000 1715332707000000 1809940707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-310 \\xd46a3b2e7b267a74ba93349f7cfe7d2938aae718c7f315d6d603c4ee8dc22e1d352520e7d903b10e40f6ebb93e970201f03c64ca5e1b164257d3d986c1da3205 1 0 \\x000000010000000000800003d8f3afb61545dc5f5de2de8da572c847e0f37c38b4762f732c38645e586f9f7838c475d83c896e5838e179a6458f6da1ae82f920eab12c37377ea96bdcbd3d18a676892112ac037452b9815aabe469fa5d70f5622cd79ad351f853a82910ebcd572d0aea09a6454ed2867d64f9fb44f0f6a11c5a19b2b7e0e47d7dfc949f1947010001 \\x04251f891e2d3530893a90f0f677cad4e21c974ca48439141e547e820b9c145d64e894a0d29a3f4671c028700cbb8931d869f040cc33f1bf853967c7275e510d 1661327907000000 1661932707000000 1725004707000000 1819612707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-311 \\xd6c21a1c7657bd62bf3f7752ca42ed93b478b5ef27edef6fd08470b8fea5f5a94967b91e21d2e5ba4b60d29b1737a46d52050eee2438faa72893a0c8882d372c 1 0 \\x000000010000000000800003be952a850a980c643c5a9dbc07cdf91be8d307d20ecd1c0f3706c959ec79389ee68f13dff57371f008ef7ca77e55a434a7b42aa6171fd7f24272474ebf42eb562905c9f1130c9d1e25b6b018cac312cc336eeedb2bb7d5631cf1ff13da94981439727ff9777ff943b0445c05c8ca7b59d9395d73fcbad23586612b43c24e9a5f010001 \\xff33be25297ece301acef463b52959b8d4c0a496e7d001f087b919e62c0bc9696c9ac29ecdef66cdc1d20b3d8ad99d854915be4ea75ed213566b833e0003bf0d 1656491907000000 1657096707000000 1720168707000000 1814776707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-312 \\xd836872887abcab14e4212a8092034daba1206f4e07d7a42b8f5e18e61ef8582b8488de4910da7775561d688c6859bbc17dbe3c7529967d90979f35c703f699b 1 0 \\x000000010000000000800003bd8266b1a940d4cf4b1125a705193f78ab9821c9fa59b0ad5f9264eb1f6ec5681c665a7681487c295475cf83b801ce249ee59271e0d8d3776938e5465008213b5f1f38300864b938de4a145411c92ae47a84029ebb0b3f84b1f454ad64439cd42d77aeabe37ed3adc7dd6f3234ac68ee74ae8da8916bbe8f4e6e0d8d5de36229010001 \\x33fd251c9e0bcfd39d7ebae6a65ef5dca0613befb805c8c3a021f71c4a910e4f33fe01b6096443939dad1c7a2cccdb3ed87bcd48b365eb40a622392ce8b8f400 1641379407000000 1641984207000000 1705056207000000 1799664207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-313 \\xd98a8e32c2cb21e9527dbb2406a40d554e20bf42800639168a06fc1cd42fe2481b3e1c0a73eb94b0e98cf263eec4c2a4884b79bc899cda5aa4d2dc1f4a4200ef 1 0 \\x000000010000000000800003e4a7bb0d0ae9772ada6a60eb5466e9b8c28890294c4cfc7c85436e708532b892fa7b0dbdd7b37ad82eb82476cc2d08e1923f0ae0913e44af619c07f71f8f9a374c66fad69e77db93bbe2cef2285bac32bd4fd6a8dfaf546d7a700907480775a1bdd241ace99dcf2037eda6dd86f55741d53e3663beba0f31d38d0ac83900d98b010001 \\xd353196fde4c55be1e13cc33c1e9b989c3618d320d10064b1124dd4d0e4af3d780e7e7b8c993956bc3e7f55a8a661fab102853965bb3c5bd9433e0772e269e0e 1657700907000000 1658305707000000 1721377707000000 1815985707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-314 \\xda92d80d1d6767ac7e2fa797c5bc22fcb16a1fdb453e5aeaa4b30afa82c5a9a98a90362cf41f4d2cc90a708c5549e6d69e54b438184de158ef6a100530b9ddf5 1 0 \\x000000010000000000800003add680889d9c291a9fdaf0504020d7cbb445fd627dfd3380a33349c2e84a20f1828167964cba0a4ee14be6f3adae9ca2446a0f3eda06c719dfcb5a42a04758bc49cef6569f88cb5610620d5d2df980c5fcb6757a2d199aae761098ede7c7a1ac84bc04b5f5f4b8b96a40d647cbdd0f00b4aa7f3fb868125e998fe4a2af049d4d010001 \\xd23c1b65eaa5cec4401eddbcdea6f9e302bfaa09b1ac2955475da1bc3584af1612652fb316f2e5379a166f119c7495695eb1149e802b8c56c7e73acbc3ae920e 1664350407000000 1664955207000000 1728027207000000 1822635207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-315 \\xdbd2062752e7f86e94d708ac5c59b7fa1fadf386e41273bcd91a6d753f7f25dcfb97fdbf1c58418f655acd3e668ffabc32b521fcfa02b5013c9d95f68e044f0c 1 0 \\x0000000100000000008000039a78c4c3feb0bac36287a0ecb7b7976d71bfdd4a1d8e9329606749b97f509318b88fa8f321dce4395adbae0ad207130bf02fe2b4d47485a00f62d3543abe118eaa265e946aa0d6e1c1d3d468afff46ba6ed0b083a5dacd70506958d1910b3834fb5a23bbf1c505730f374246420d513361ce43ed93399664e8e1630a4bc19341010001 \\xa19fb9ae0640a54d8740c8b51700ae1b1df46a01b6635df54f8fd68a1bdf4ed99430b073f7136c1754934b67534fac2550f9ffa1f03598f595296afbb6a1050c 1658305407000000 1658910207000000 1721982207000000 1816590207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-316 \\xe5ba282a317c7462906cc4d632e54a8c4e03295e0594ed23e3568fd8af9377b446ddb11a1ed73eefcac47844d793289a86b1817cb7087716375c00a79f6e9dfa 1 0 \\x000000010000000000800003a3da3cb31f0b433355c5bbd6b75b485fef822f28ce99bb10fc77a645e372eb87c5eca30b3b823073772a4c995e7febc0f86f2b12b303e0b476a3e9a90806758db9fa9b4ab4d9cfc729dbedb89b3496457f0e884890b9ac3ece0d5e3dca882edd6d158b4a8c95f5b6089441ed880272512516836b917d0dfb5f4544f790d09481010001 \\xf1bddb042da83d6dc4d478d182a0e18d623b8e72adb13518238eff9261f8eaa28e7cabd85a118a6d9cc22c264157e2108db9903626f0d4dc680155756231ae03 1651051407000000 1651656207000000 1714728207000000 1809336207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-317 \\xe68e495779046abe8bbe024614f01f90a304be5a2bc2281ba3c66148571a69c8ff6a3de56603cfd7243fbc0ed00fe7f018740c0c1ac95750f600b49e77f443e3 1 0 \\x000000010000000000800003b752fbffc7bd758efa5f214c41de9b705a44cf33c2c26cf0bbddb4c2b6f9cd722d85d38333dc4a51c962428e04f7a9cd11ceb1d91d1f483a4eae59a2a35b182678b8730184bae739d8257b5b8ccf00eacfaeda5135d2f4073039d4a440123fef07665d2dcbfc5a02708bc4826032795871c25e16a841d3af6c629c612c1e6601010001 \\xe250c8dc357672fb68f8dd63157dbe92f17fd447b049e5599593e06f00f407968df45fa75325f8c959be59728cbd6ad87efe60cc2bca8dbf638617be26d20900 1648028907000000 1648633707000000 1711705707000000 1806313707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-318 \\xe67ea8cbeb4ffd01c033233b9f874ff07fb774a1c314f23b9d0459988fc05ed374528d00f0db57fadb4e1bbf56cbf8597397e85eddecda3e5d93015130cec2c1 1 0 \\x000000010000000000800003c95d7a0112d3192f7c39bf87cde32aa7cec6486404036a7b23de3abfbe0dbc6c8cabae4d6d186053ca829f103685be3765fbe26c2e9acab27d41edb522691f68c7dfc3b665a58e1dcdfcca26068d9670e63703cf72016a0c18adf84350fda9f1e5c03eaa1053822ce2b31508930b093741045b347fc5d7b9227947acdbc050fd010001 \\xec04a11f0b5aa505558416b2deeb08e50888fc2f867d01da0f62788677b12c72fa7d5d9f24a8664aef33b582cc215b53f95cca9b1164bdd6d143c434d3d01f0c 1655887407000000 1656492207000000 1719564207000000 1814172207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-319 \\xe81a30b85ae51e920567f998cc19e41e08c9b1e278159461bb19e19613f8fd844b1c5f9d745c73853e341bdef63fc0ecdd2fde8a9aa753e83092febf3a5140e5 1 0 \\x000000010000000000800003b56aaae28f33a6644c067c862d9df384036c433aa7ae45b2f033df26ecd5fbd935bb8c8c3cdd8bd1b287962958b89760334f8171c12a450ceb41c263d854425812a0296fd5393dc82f29b4851b1b8f655650713ac354f6eb00de369439439d357a60a631e3857717b665d5882b78fca02ff5804b75e7edd3fccaa90497147a09010001 \\xbcd362f7d7a17c30cfbd9ab8f368807a184d3df230dadea1fb74cd707d2754c439d630eaf7bc94b71dbd9a5245c0475b35266dc1a087791a9e9cfbc122294605 1654678407000000 1655283207000000 1718355207000000 1812963207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-320 \\xe9ae5f15404d249c1ad6a5d35755a7a0cfb1d362ff5398b801e9c4fbe1263c73862712c6c2577affccddcc90185ecf9c350865301667a2e3a6200529f7097af1 1 0 \\x000000010000000000800003e14278085a33b4ef86f3310d94cde6f91d08cf50867e86009c2032f7b901fe463904e247152973623b3855cf2bb4886dbba4a6ece5041d1e378f64b0f2207ca46f37e19039918c9efddfe85c6773fc3ef15f5f7c82c2c9f6ab622cc3dfb722b19c37238ce9118fe927a584026900b755f280ec60b4492726cd65e7208c14660f010001 \\x29844509c3f55a38a164ab0679c25a6e3ff6f5433f97974e65525ee5a792848095673b47b9c37262b4a660c0481db05f9eb1bfe4e37b4a054d795d18a4df610e 1652260407000000 1652865207000000 1715937207000000 1810545207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-321 \\xe9ea06d70ad5ccaf8ff149b1e2816106b37ddf7dc7df4ccc0fa9cf780f5b4ae251fac2c0ed9ced83b77a70f62b95924fafa6cb2ff2723b796729ae9de0a9f5e0 1 0 \\x000000010000000000800003b8a88ef092f51d034d7506e1acfd097e03ca196475bfa20e1e89565bbe5d5c12cefa639436b93640ebccb191ae8cec76c01a1454c393fc40d452c202822b00d3831c643e3aa68b7d20d1bdbadc115be25bbc74b9560af2514fd7ad4a51ff38e176a2d1e4f1c81bce9541f4d45a3dca5800b7e1620667e73909bc1af0d894136d010001 \\xd1d3815dc9f17f95c08a877709d8cdefef24cbb54fdd7f1244751e2e8353f8723cf138f085cbc0686d5c166d2fc65e35cf0fc3e33da65b70b476d7c3cca8d405 1643797407000000 1644402207000000 1707474207000000 1802082207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-322 \\xec6a29b0ef850d2e4c0a764f06a4f580b2822308f02f401e14b0f89c05e17e363d84f5e99b2ea136c8acddc9f27344728957d9ee5c260dc0d8979fcf4e3776a6 1 0 \\x000000010000000000800003c10867de74e267cf7d7b797a63511f4c93bd498b3bb2a6113bc824294f55969ff1064255ed74bcf5c92ab85ac9cc773541066d502d3958b9d736f1656edc8824de89cc2f6494a7dd87bd38a14cba006f4e261b17eda653afc25c209be4962b75e5557bb0bb918689a6b1d8b29010f7e1064c0fba68a643efda9c16ce367a1a31010001 \\x365b50876c374f92363fd2400b10a7decb9bc1ec7a974fcd9886ea75fbaa4c826de2f46f9bf0b3cc738aa4dfea1012a2b983c445d133ec2e14afd1f1d3ab330c 1648028907000000 1648633707000000 1711705707000000 1806313707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-323 \\xf002d164f28e9bd4ebcbd34c4288149fa7b5507ea2f13bb7d198afd1c666b8d9ed262ff0280103f07361002db4433fa4166e3b0bc91be5fa38903d4941b0d80b 1 0 \\x000000010000000000800003a9393898a097cb98b669e875713800b33d85cd504fb76d374bfb3a2acfde2de8f44be763572a738831eeb6fe85a5540ec6146d5b94bf371822fd51649b933a66bcff158b494fed67490bd33295af8025830488742b5b4ac62016bd784d62eaef9fb6bf875d0de70bae530896df9bb0d00d8414d725f32091faca7d64bb311c73010001 \\xaa4b4799f481146b6fc399c4cb5d43775245bce19c195f839dae10f0ee51580526859c66b49ce43e4d058645ef720e3c3f07e3067b92a6d7805c634918880d0d 1644401907000000 1645006707000000 1708078707000000 1802686707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-324 \\xf52222ae43288189567ae797b0de7fc4dc84bdc03b6d22d01e2d1ca4a3c512960141d5d7fff97b4bb1b598a0c5f92a93caa3399b8f6eead2a11ba18ace7c1e1b 1 0 \\x000000010000000000800003b857f47e4e6a898acc5aaf2377f8312d8f33eb775f363fe8377175b180d0d8e44eec9c21c3f6843e28d6760cf90670f1ddc5af9850067924d5fac5416da50cd6e36eb23962e24a9f072ff3ca200fe7fa4fd66ece1cedb21b8c4f3f098ba7da8a78a9a74136c35b13aaa299982353651a0ce8f8ff2d27c01186a3476b6b2a170d010001 \\x5b9575413d1a3257734e9d378808a3a515029cf8702643648cd4feef56aad93f27f175793a552ec55fa0acae9e2028a5683ae7133927912440f01153cc41560d 1657096407000000 1657701207000000 1720773207000000 1815381207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-325 \\xf78a87da59938fd0cfa083fc9c72f5b2e72fe9351614c470fbad019a5b8cee8847f932b50011f8eb6205ac8ef2a66df046ca101d284bedc3666298a2fcc8fa60 1 0 \\x000000010000000000800003cd30a55d53f168623a75b86da1e566035817aa59f5caf89dd3b853e00d6b3d112fa865b072db9724aeaa389c13fb25c5359abdb80c1ec1f4a1edb27c6c6fcb0fcb5a48f3b78b224a96bf786cfa82d2cac1dab6ccf8e54dfec271fc07f686af225e2e5ba0495531f279925313512d1a2969e19eb6ec0c98264aea95fd00b9e199010001 \\xa5d4277482db7c1b60915e14bc850c85fb498dd9a3f1e431528c7c5076afeed52e872d2b59e1906a19e3caa5f30b56fd4b4bd97969e8be2c982a86fb6605bc02 1664954907000000 1665559707000000 1728631707000000 1823239707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-326 \\x042b647cfcbcfaf49db835ef425ff71afbdfce69e40c087b80e230c13e17cbf87faf954d6829ffd2fcbb8154122491e4fee8bca4fd6a2b92f91850b98ecab691 1 0 \\x000000010000000000800003b384a1f28fda558724365a03cfe6fcc048c2e80e795b5309818b528ad0092a40e4cfbff34f79b564b11fe323ce3c3ee400d9924bb2fc4babc77db953c36a3910b197e4b6935516a5566488ba71ad8cb3fa0cfde08276feff87b19b3c133ebaab600d2550a818754874341f6f53e89bba18434503718e8d49bd9641dff496a999010001 \\xe812ad28bcb7e0ff2c4c13d1694fe84f9c6034407e79518e671166471eb478ed19c3ae4f959d228a372f16a9759c4415c98f8518b8cf8d69a0c044d21049b30e 1643192907000000 1643797707000000 1706869707000000 1801477707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-327 \\x07af67ce08a4791a14fb875027e49a97760eec542e8730a5d050edfa6d6e13a9399b42e31a3e230938620fff41c2a295a7661fa9498ff8b9a48e16117b72c5f8 1 0 \\x000000010000000000800003c8ab8f65afac43df2d488e420935ad0ebd56959de6f83a73379397e25c159fd3d2eec9727fe50dd1c8065eae74ef3a11ae331bada3afc996b2b965e6e2840c232f4a55cd1025e81a238c9b267899852e12d7cd3df2817eb460753c679eccc5d0448d87a930303ef5fbf657288b2e8af81be25c2578815196084df0c2f871a1a3010001 \\x0faa78e9a3c8870222b6c9e055806aab4b7e1f54001b070e07f0fe0c634b80c29dad9bbc1bc8fc150c3c618db926890479425feb74bdacc01448b3178b6b1d0f 1655887407000000 1656492207000000 1719564207000000 1814172207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-328 \\x0abfc5db0a177a2172f0f8e733b216d7a386fb73036ac2ae164960e891c95efdf66f96959f458f826e3e286ede33b48cca4139d060cf7d75b1e07a348863830e 1 0 \\x000000010000000000800003cba50080bdd973411436cee48e02552d69ed48700096e13d197adeb0af292e3257d1228e2fe58a0fca5fba3acc0db7aa09f02a93e62c8108806853f5af5956936789d8a1a08f79553eccf90e617f731a3bc3aa49ed34bf9fc8ee79696d9838fd18743e1c96b4fc57b32df3d4501597e0297230f76565263765a3d7f480232311010001 \\x8f2eda414c06ce3b429b0b49854ff8c4d4b0b211066d06045d4bc32567ce5d92a03a0408adf148e77964911e2a721d3257a9f6b190e0a75567d6504422ae360f 1658305407000000 1658910207000000 1721982207000000 1816590207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-329 \\x0ed30b0ba082b9a32a43c2ff1892b3f3b13c8f6a3ad13353c86541635956f14e0dba290c21e7be7511cc37d4ef19643a5c93c41ca5d22c3b7e2c216197bf18d2 1 0 \\x0000000100000000008000039c311f5ac5dfb9ba0eeb84d5e732d5e34aac75b8bb2ad50c921ef825434f5679096595529020d400f9d58b9b55e94e3f357b8bc3d53d344f4268b6bafb1cfb8cb866c71262f8ee253e4c98d64da929d4d86eec23fa2db98c9f931b3d583daab415965a55c34c7c74905915a186a33b0e50a42dcecec7302862e415344375288f010001 \\x42b1119ba4f716627542cf8db80361364b559fa3b79d23207c26a1e5d75dd563b2bc486c90409453841abf18e290e04ff04d32d5ee52174933b64894f297dd04 1660723407000000 1661328207000000 1724400207000000 1819008207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-330 \\x1bc34b1f8140adaaa6909ada12a65205fe401fbd3be39fba59e5e7c1465de2aa7702cb2dcaa8f7ec9a0d2e31d757dc520484d88345c480061a32d04d4bb82c78 1 0 \\x000000010000000000800003a9fdc3fe1b313ac8556cbfd9f31145ef59fe07c9d80d0a66842fdd9370479c51287d8805665a7672a593be255be8bc81d1015a8837ca608b95d84d1958c40e73c96f2406fa5f7543777af4d95f379cd7fcee80496a235b97935d8d1fc357c8b5d05d6b28eeb36ccbec2f3e03a4980ce46b148771e8eac91cc6255f23330f9fc7010001 \\x39a2496bf087053677d9aa81f8042c8170585e1127eb5ebf2cf200f4ad5e950525dde4542e636a5030f6d39caaf9f58920e3924bf5bd704abf4dc601842dab05 1652260407000000 1652865207000000 1715937207000000 1810545207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-331 \\x1b4f0ec9000481a4fecbe0c5025da85add4b6944b24af88a2512c7daf58777095aa534cf27f9c9d7a9357e5c58d04e421ce81cc32dd57319a6e94c85c117d7f8 1 0 \\x000000010000000000800003e00a929d376e13d1ec72ff13a6ff0a8bf86c6b28f6ecca5c027fb30fa60bd832a3276c6c37ec9f97a4c52106115cccc64033116f870501e6892fbe61035b1ad75785008eff6e0b4b08cd4035c4330dbbec8875b8db5c66870532de85f4f02de72fce272e2b25bd6a40d9b453963fabdf08eba30a86dd99eb6e06a20637c52ce5010001 \\x4131a87c619f56c7058c1b30f6eb3b3b63ffe9c513a055618a0b385be4bd85a73a37c45614e516163e3071bfaed9699bc35b476070035d9c7faa5e975f0f7f05 1645006407000000 1645611207000000 1708683207000000 1803291207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-332 \\x1e4b19b59fa6ebfdca50eb3fccf6661d21a6b5705f1d31e1974d2b2088f4cf29b4c7342b4ba32a77464d184ed9d4eba91fa1db3b626dc268b9d387c7bd83fe19 1 0 \\x000000010000000000800003e9cd9e632172b226a067ec7878da6b74b04094096753c07c7353ce4b5abe146ccf479ab25859c4adbb7651a34fc6488a544441e4f3c0a0d4ecc37a4c23d338fd2073e580f189acd20d6ebd923c210180bfe33db881455d82752d7bf82b4a2c4c88dec192989814f1ca35d17935cf731db6f6e3d7922f38639d4fbb74ed9c0aff010001 \\x53faa76c6ff4a11d085dfd149958b42a05b3b4dd692655aa82a2ffece2a1719c6d4abfca5c5d59b278263ef1cb1bbc5d6e4a5bc0064e48c30d75c5750f4e320c 1649237907000000 1649842707000000 1712914707000000 1807522707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-333 \\x1fff9886f3017abe4490794578a073047d9e3d6cf055c759665811630bb3a4b9384e114d9af7faf1789323aab8ee6603380c15e1761a6c4090dcfd4a5b97bd1b 1 0 \\x000000010000000000800003c905b2b869664d9fe200d1aea2fde594ac72195c35d321085e1d66429bb46f441f33143755088f4e3dc64b4ba523acb745e0860bb1b24de7410949ab995bd41a70bc19ef990840232cef56f1c845bd6e5bcf3dc3e94344ab82b62aadac39c15bb59f0eeaf29007676282bbb7b83944dc18321d733705cefcf25cd705146dbc73010001 \\x381f690846c2d64cc0b34743ae6ac7bbbb0f8dcf6ca37c122efc051ce433b1dbcb86caa00153da6846b500f5a1908d43f01c1e95ac1d74170ed47a93dd4b1f06 1660118907000000 1660723707000000 1723795707000000 1818403707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-334 \\x216b6963c228adff11f60fb12d4b887f216b4df0f3da636fe9a19f9cea2de43f04ff541c643088845730b170cbe90dab826aa81030c6a9f18e128dfc8ffdac24 1 0 \\x000000010000000000800003ad47dceef6b31597f1d157f99af0190e6ea3fca47a4a11502b042c716fac747aee676bf8e34d5cb8536b24c7882730ec35c754b5200b060797e946a72d44c69da90e255c917dc2fe4dece9478e83ebaa5ce18e690c784965b8a3ff9bdd3566f0b57789090d47f1dcaa7bbb946d36db34a9c097eab8ee383f202174a1242a5f79010001 \\xb1e5e169fd756f6044e952384df5ce04bca78ceeea04d50ccd86f5dcbbc0e4ece85ec0bb220fbb68596809f75ff49146fc76ce4a4573cb681a157230277db103 1649842407000000 1650447207000000 1713519207000000 1808127207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-335 \\x23e7ee83be30246d69475ec4c3f7265a191f56df40f258c4f47aede8743d24fb48767345664c8a9c67e33b91c900c65786f29c344bc64487d3db25216842489d 1 0 \\x000000010000000000800003c91dce74d8259833bf450015d3cb340aa62bf120db3cfabc87e7377c4baab75306d4d499920219c9f2fb3588a1aa2c0617ee97f5ef0e5778cf05531f91b790e0bed62390749780a78ec010f06e2d58ee2e47caa5cd7debe85db18838bc080dbafb74f264df67e8a72fb86ef0f023076d2bc7174e76fe8dc6a98403091aa13a25010001 \\x1f7587d6f52c06fa3f1eade293abfd5641dca95eead8528127ed15f9a5d381a8c51e55c0fba124424dac37ca0d61fe6b39697dc7b903115772db92bd1d74410f 1638356907000000 1638961707000000 1702033707000000 1796641707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-336 \\x243f22c4303c91d9010b247bc143ec89c1b3833013ae19d3d379a743231e2b266380560880e546263455ef0876181ff753a4557681e17daebda712dc0cebb75e 1 0 \\x000000010000000000800003afaf7fba97b3b6c1ed6bb36056074878018dcb29d7bee135f0d8451728c75f7effec0863bed6eaa9007a4d78ac7feb4897ef46d8ed4044180ecda5efc2c404d87e70e4febc24caa7d17970cb6358b244f64011abb46c3bfda85160cf427c14ea5b580bcef1b33de0d17355ccb4911aed0e594d00c68e36775aa5192d8f031b4d010001 \\x40826e23747db6304d73c329690c7181fbd9014ba58aa7e7b0eb4d4bb38d346d2c922c5af0e89dfef39c8df5cf5a3a38d77c53b088a955915ad44e200bbf2a0f 1646819907000000 1647424707000000 1710496707000000 1805104707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-337 \\x241fcccc138a494f260b9809b1c97aea74fa7b00d0513647cc6022524bbad38c6e23cb3837ba6649595454b63635d4dc963ad061cd6337c4acf4766558b82f8c 1 0 \\x000000010000000000800003cc0f82b988572c955c7ad0afdd27335ff9a57dcba99eb3713d8b372ea1ec66ca09beba649f6b19578a366820fde544f2167d59d2bcd8ad8dd75e26776650ac5906dcc3d2c6a05ff05bfee0bb306d71e0c9a2027acbd5da47d76702ff0d18c46f4baef5143f15b4da11a71c6b42436196e6374e1a260d3c939671c9c1e179ddc1010001 \\x4fcd2e346cee8c25bac83f2e3df362724dacf9cd98f0b5a07ac2d15d47f193b3739d4917399988249fed625fa60f0bdfd7bbcf27735334a9d6b2e6a66a93b004 1649237907000000 1649842707000000 1712914707000000 1807522707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-338 \\x25ef1520ec9509f11dfe95d634a8d81371247f183fc63c5ff6e4bf8bb11588a4d62c3f1a093f97e581596046a2f99f108f8b83ae40cec1db9ff0073e6a77833f 1 0 \\x000000010000000000800003b2468ef50f7f355e32a278d6882baa9c73a1b1cf4829141caab5009e7b254b8c8581c9bb918a2d964a641a4092500b8a8d1b9671957bfa486bc9efbf107e265e938784e8303813c3b18495f2526e2e761707e2ee21c399b7dfa046c1a3ab172d05eacdbcd41b1e685603a701572c3f91995796e0534467a4c2228e751de5a937010001 \\xaa9339ec9c4b91341c9c0a7432062835312be99a996da4d3ca4786558cd27958d0abda7034ae3bd10414c006777bdeb2145ff9cdbb76733dc9aceebfad76700a 1664954907000000 1665559707000000 1728631707000000 1823239707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-339 \\x25b7a921589822958aa19e0574518e95b21cbb28c9f73b4bb019dccd2a2286f78a7c6a1e2563ad3a306f310d300e773f9e4825149be366fb48252fabe2b18205 1 0 \\x000000010000000000800003a71bcffe16652d8ad9f9ae2722d3f75d8fad80f3b79963101ab39c8840e1dfd3980bcf5779c21d9b925464c3a432cdb8e326671d8a75445233415ad4ec0484f5c9b3c5a3ab50b721dc21a01a7e147f3d866c096b8bd7e0f165266e27a69cdd6df7198b621e541ba3a782ed7bd1c3902e985c380f55831421aed072369572a225010001 \\x09d65e376949716c6b8b383844e279954dc1c5552361e81c2fe64b05b70e0c6170f4130fef1550b2ebf82289c571002baddc009a7daffe21788a9fae0e973f0a 1659514407000000 1660119207000000 1723191207000000 1817799207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-340 \\x271fe90d17b43067d3e3209d753b1aeeea2e177f2f370286948d24f03e594d83e9a8e969945614bdb4044feea546eb8ebbe7c9104bb7852da0c229e47907c0d1 1 0 \\x000000010000000000800003dfba9b8c837eecdf0c301577f1f06fd13f90959f5944a1b1c3fa45cb76cb6a03f09b36d4dd2af8fafd30b78a78c4b25c76395ffce7f34359fdfbc6c6a252178f50603d22222006153d378513feb1370d319ac17be56ff45a77965360b6a089dd5347a7ad9133f7e0041c462b0d4bcb280dd7762fb16fee29da968a67e13d4ee7010001 \\x94f547d98388477198f2ef2f03dda484e1362ec54263b65340565ce9e6fe8e757bb49772dfbfc317a83ad86d2200219f81327eae7ef53f3256014309bfee3006 1665559407000000 1666164207000000 1729236207000000 1823844207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-341 \\x31d743f4278457a95ce33e665926bbd8a71778a928e7a2efb3ebf01ccaf26f638a8d342c7c9856388b5a545ae47d3d622f2f2d207f5385922c05de5773adcacd 1 0 \\x000000010000000000800003fc30ca857288bf5297ba34f97f8d6e8c35062765bfcaa331d168a3db4cd30ef31915d9eb99cae40a41f0da58704dd5ef6b2fb1713c352a926fc6bf43ba2a06739577eb98d44c2ae47793529d71bbf1d30d4ade19ef00fbaf508f8ae079b3540cd9055f848ffdc9b34fdfdd6ec5704f252c62e257211157a1a370f271b75ae4a9010001 \\x879de908693891ebff84ec2b43b6cb2df749588035987c11d16439fe2f156b5e3d84ead6e0c55997a47bc27f0fd00da0a88f4c0bea7e3c5433a09b9bcec0ff0d 1652260407000000 1652865207000000 1715937207000000 1810545207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-342 \\x3247892e072261b3e5660453c0511f34ca3a3c67ebf7880f2fe0506e72b8658e38404e93635237ce938524dd735af51d4a34228b934ddfaf15aeead780894f1d 1 0 \\x000000010000000000800003bdafad9b2796108c0d254c3f61405eed7e89f9587eff9f5764387488c29a307c7be333bdc47ae403fc2f541055655724ce1a92bf13b3fa8ac262fb6a04639181419e1707ecbe589299d8a638859a1a1842ee08a777169fa83e9100eafb68ae5f871d0830d78b0c6614b7eb1faac8bb84ebe9e8460b41911009cb6d83b3174241010001 \\xce9ba8b47a38d1c8bd0dd25ae3ec3dec27deafc3e40e2c982007d9f779e09822b4e14b77c52c5e5524d26428e42302d2d91767b2028b3834c8d22561ab081f07 1662536907000000 1663141707000000 1726213707000000 1820821707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-343 \\x342b17b631a6c6be5cb0471fb4faba3beb251f1d548c60fe049c05975ceb0a80bb64379021477039161c95b9a4f7b84d07e0f24a6d5e19cd5384e097e976c38a 1 0 \\x000000010000000000800003c7504feefc3697b323fa0a7fc5b5a136dc42bc30f56daf0847b42b55f12bc3df1fb50fd5c4b23e24a391d534fc944e007ac117a44adeffe83b1d94554757c593fa44404f27c13f3a08d7e4af4bc7fa7198cdfeea6cb3eb67db9c80fc9f9c74b2e9faffba11c0f4dc39777fdb72bc24e39c428d00494e72411365a96b1ecb06ad010001 \\x82715e741b910e914b40d387b9d58d2b05a857b45ece9910b41eef6666d03e929b7b8ec0f6fbca5ffa41d85908c711e28a992f52962702ac903067f422c7eb08 1660723407000000 1661328207000000 1724400207000000 1819008207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-344 \\x376f76bff4809bee946bff4cd44465f220e1341b7d64e586a74d0debbe298e4d6cb0dd7b9958e95dd18448439e8adedaf8b0d789e7407f3888f35cf256b242bb 1 0 \\x000000010000000000800003cf0996e92119f018e556b0cc1595a64c8db2cbed40720863088606086a92e7f90ace4cc459dfced3cc91dcff5d1ea18af658e35513582f0caf3104fabe622d46eb6c75ea511358017611288f86be3f2e1430c8933886822f52ff31a138ffac2715ecf7abefb0a15bfbcfd35112c383047a98057466701452aa05286b4335098f010001 \\xcad8e39d9ebda0807c85fe8b25703e0f1b9703e2c6d7fc9751b5504d9223394af9139f5ebb7a00499a22f86594520cc2ea63b0b2d6e58a25f7a5177a64985402 1641379407000000 1641984207000000 1705056207000000 1799664207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-345 \\x3eb7f0bd0371d5da80504e81b23319fa09dafc15805177308da295049079e4e82128b37293b36a6d63bdd634c21d7c108be21d939cc1c9af7603099342657ec3 1 0 \\x000000010000000000800003bc1b68c6cf627bf73e439e403e61244997ca485c48d975a7be7eb9ace314711c77d3b4f2bab6a175b6f458668a1bd6b39a39674edd6de252827b1e5fc8d03f3b46400f8f117bce2b36555c2a3d5c43173c925dc55a621f648f6ba4b8999995ac911756b0543a87b07fee6b01b84d2b1e194263b652b3259401051979c248320d010001 \\xe2ce5fe29ceb905fe19e665e6f51f74b37c1e7e07986042caa9c284f996c642d8877bc5f622a787c24c4df25f275ed69f2760a9fa0cd56e16cc1b9b8de9a3600 1662536907000000 1663141707000000 1726213707000000 1820821707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-346 \\x3ee34ca83d558ab69d7cb095cb88ef33e9fdcb0bb5e34070b4a05d6af57906acdc3ecd6b5e486d7a23c455fa223d2535ef6a36d0ba6e94c792bf83a4970b275e 1 0 \\x000000010000000000800003a457e58a5be2d20fb78af304ce0d4585a7c85795b0e0d239296f0f07d2971cdb48fb143743c96a54bf59b7619f60c83d29596b9c3c91f364b5c6aeef1c68d87e05997016a1ddb06f2165bbb83414fed5e7d8c05f9bea645a700d26170b3f5e869c47ee85b9ac2b43fa38f4bd3dad38e88608724f2a113ad71fd61629f15c9ed5010001 \\x06e6bf260ec96d594bd6fa2aa8c79781ffeee878ff1da7791bda4661907afc9261ec5063cb97bf65eef757769cd0d8a1c86c49abc2c7a775e3d42235539b1300 1639565907000000 1640170707000000 1703242707000000 1797850707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-347 \\x40d308495ca8c9d817484e4f9a829bbd5f968bdd114a592a636cefc6598dde4dd665950f16aeea5477e4fdac4844e976b4840a2c9f3eb9bc512d9e8eda6b23ae 1 0 \\x000000010000000000800003b92bb46fd8891f363eb16a9711cd384f50c71dece422b34774251c929fade75f98c87cb20ecc41cdf11e994224a19e852c26a4227495d66f2d40f1930517c26cd1e04654baa7286936b60c357ff26927c013b0d5e2b54b43db1dcae021e3181bae98329ce6c995e20d928acf2460a9bd45b2fc0cdbd4564e8e446daf6ae443a5010001 \\xbc536307d58d8c238ff88e3e9e32f3726a528c0c11bb6146af7aea8cef4a73c747b0674af68725a3e70b430329312c1b01f802b27643ab3b8d6cc91106066808 1659514407000000 1660119207000000 1723191207000000 1817799207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-348 \\x40af2ae8c1346efe91fac4e9ae48e226e51d88d145e1b8fb71b98289d8f640ea001cdfaf55b83b38aa9ea8d62e934cfc2222b55b9c32767a337046ee987a68e5 1 0 \\x000000010000000000800003b7f5776ca6eb8f667595b2f74f8ec1e6155ec99394f2507b4ab8188dfb5dedc19884f24fb7d9cbcc2ad8027e76e4d91a8e41086f23f7fd18fab0f15dc30588bf2d01e8d3c5bc02025311e98858a37a672543c51f342764cb74ce9be47dd3bbd1d3958572a95c89ef1140220fb914d018d1388f5d3c1ca4fdb8e00b2397021125010001 \\x417c2e70586cfcd1cbb78189abe0a87161dad353d6fe23df1fa310a97003bc9d60dfd482eca4c05fc81208b886a4aa37802c7dc4b6465e1a95e9862989e9de02 1668581907000000 1669186707000000 1732258707000000 1826866707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-349 \\x42135986613a618eba058ebafd6ed1458136d7c4f0f42f591ef801513c98fa5b6ff0eef10db5c332486ae7f482313156a46303ba893909d92e08c365ab2e665b 1 0 \\x000000010000000000800003abf1859955479097189c911dc7543f39ade7f033f6952d0f9a373b451af34310563120f36a1c3937a38a745c023ca9ff1eccf426143f9ae68ee31f6997e7463c31fa5d80b406532a84a319e0376391ba93f7713d95dc0dbec4c479788ea3056bf2623d18e4d8e670258ae477df632b6beb6c5ae4a20c3f70b12760b92561a8a9010001 \\xbb3f53cdf31a62eb0e0fd6230fe328576f274b7967daa7d8cf26593d7a0517c35e7d7f2f103e77c55f6c47f8c8ce2f80c5dc8da3b8d29520552fe3bd8819e804 1661932407000000 1662537207000000 1725609207000000 1820217207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-350 \\x4227ea57795dafb2bc7a8ccca72adc7eddb5433bae60362a3b2f90035c2d6be791e46878b347b1399b7239436ca25259f236ca8211ed1195d34607ad438e76a7 1 0 \\x000000010000000000800003a75466dbb77656fac778461424982bc7852e62a7314ac57fe2ce603d111b762d098acb38fc70d8eb7a884ba4ec4649382df045284dce20279050805c4024c9b18dabc7a4926efedcad8be472f2a97874f4ac3a26f561c39a15079f0dfee4d8585699e4e104fa8c3fcba2deac3b8f00da0fa4c19fdf90dd53b2f8c10918a34e75010001 \\xaeb04bc458915b474f38ab1eb6c3ec9e94d5ea955a0b02f197236c6f03e12c1177883a9fbf06298008244a3078c732969e067d4c0d0a023b19f902a1c89d6000 1655282907000000 1655887707000000 1718959707000000 1813567707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-351 \\x44eb53adcd4844ef2746294765343106ec22fc8f7e0a9e70d9c00d5900548cf5a8cb1bc997c36c615965cd1f28f08f2954c839749462394fbca9d32d2e3db56c 1 0 \\x000000010000000000800003bd87536c3612dc3ce38d5506591fe865cc222439ecb88996e16866133cceb524da659891f97a9e97cb5698bc7824c93c9bfde3fa05d1e2a98040c1876e00d0efd8ed131f701f75e3fa187dc8c520f12c1289fce3bdcec37a399e45a091da84893da29bc20259dd2d0b1e7a7e587955913f860a2d336c620eb070471476c6fa45010001 \\xca3db5dbad4b2f3af2f4a43b90b5fe4612efb1db5ee75eb39f5a649855480430d92c4e5cc7991acc1f181b2a552a2cda4a18fdead3f5f37e6609bcc4fd1bb20a 1645610907000000 1646215707000000 1709287707000000 1803895707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-352 \\x49a31a3ccaaa25ce12fdf8dea809e58bae7fae65007a45d22e7ad9eee3076bf3ea385080867f53240c02b9d0dfb17396160c970042450baa12883ce7863a08db 1 0 \\x000000010000000000800003ec72a24de9c21b6368ccdf5233dab11b7a3a881baa29ad8df498b7697b99fd3ff47a33715e9fffcb310f9bf8d486e1130a54ca5ec148e6f577bc552bca3fa62082d485062d4ada39416958d90822518b54c8657372bb331df71d4a334f6d5603da67c1c71d6d5938859639db2b74cf90fc187311a7b18b434e8133ffb4bfb3df010001 \\xf544cf71d58926bd0fb9c7647ebf2d687be993938028bab9a78fb2ee73b9c2558347475887786dc4413eecab315ae3ecfc081ad6fc8519c42a47eb3cc072980a 1646819907000000 1647424707000000 1710496707000000 1805104707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-353 \\x4a536ab5ea131478b12aab71df54f2b8514178a9b7089be704dc836ef1423538db6f6fbb0b77db4edd81061f236d3915605c2ed83af7c820feda46f19c8a8b46 1 0 \\x000000010000000000800003b5204e591a063de78d6a0acf90689295e7365e96e4d4f52cdbaad032b004bffd7e7791e3b9fd5e10122960736eedd48aa8ced1c90000478b0f494a1cb4257cbadb13266e10a4ba450cdd600add61befbb6d2fdb57cc8aab50e4231e1c6b76b693e0f5e624c0e99531c42edcb789f76e5c3cfd173731c857567a9f993d077f8f7010001 \\xefb5f12dae78608955517bd59a63e75fe30cbcfe93df06e8e853127401943b2cffaed5edd9dd3ecf6cbcb52b252e9cc0e508717c34a133d0b00a147ea1586301 1657700907000000 1658305707000000 1721377707000000 1815985707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-354 \\x4a3b662db6b2bfab6d0eed343a23ff45ee5d1c3a0838b0af881be201e5e3a7913aa1f0a775b5a9aa6422b207b0f672746a3aeec61310ca2f0b24ebc2b37619a5 1 0 \\x000000010000000000800003acd96988721d004166a78e6b323f843eea3fa2fb0431ffbaa73782776dd4fcb7206339413722e0b34d313a3e5090fe088b3e3b0b1a9db4e46b465459794226cb09c20735342a9aa99cd597005253cd9911223c9e4bee7aea385d83be048cfea666bb00501c65294817d40632a17efb16f9e7fe4c73f3f809f9614b3bb72148b9010001 \\xc9666aa40db731916dbaed05775d8ff566a3b6ecff85f657690b92c27e461a12b6824a23129ca55fd1e3f79f8b8654d5d33bc581a1f12579587ba1797faaa407 1667372907000000 1667977707000000 1731049707000000 1825657707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-355 \\x4e03d88a06399c8999ef5c18dc318fa2fec0d82130f883e244a97d50b7c413eeb0752bb3de496f2f0fa40f2afe52f996e54564d81d5cdfdef2d3f37d3214b91f 1 0 \\x000000010000000000800003c319f56955a9fd9ac3160f2d4cf905ea8dcb05ce037989f1f2ab9fb60717604646db1579168ce18c366ce99a98c347822c7829c4c2873197d7eb167a15c338e7e1532696a268a065133caf006b5104937b1f3eae703dc93f28a6218d83f43a93727a56b20874d5b19b7b123db8ae01c71010ef3548e279e7fe2c529c291c51d1010001 \\x09e20bae8f8c8440e1004f2e3ed15f9e2f4495086e908ec39cc062cf69b8a8db5f14eae500595636d22ce2537f2e69c927ef007c4c1c30df103a36cee5601c0d 1669186407000000 1669791207000000 1732863207000000 1827471207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-356 \\x50975260f940828b3f235f30fe913c74407ea65fe62fd466f4ac1dac5a374f672e9146d8345603c5a8fe42ddb7ed416f191a4692ef2a4d9e42359cf2dd3b411a 1 0 \\x000000010000000000800003d0a0447d328ee8c1b3a275aaf82d6fb3349805907a30109bfc1a6d159343b9ef2579b8187e9c5b399f27f002f7224350e77fda64faab0b5b2ad930f05cf4c85653b44c1f95f3b3c6671eb0395abf210a5817f90660e94360c48cee9994e53cbd97296d7cd5bb94ea07b25932c0665e574d13d9e53d99d0542b7dc37bb1d52cf9010001 \\x70d6d5c8e387a236368678530db54b46bd27615cd458c0b6a006c76e216a9311579fb70472dd6889524b6179570deade84803c6caf4e47a1dc82b7e18add9c0e 1661327907000000 1661932707000000 1725004707000000 1819612707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-357 \\x519b18f32230deb250235823914a31fc803396254af99ddb2c3f352e46303535632ee4d2ccf13fea394bcade70ea5096bb08437faa635efc76eca26bfffbaa4f 1 0 \\x000000010000000000800003c622b37c9fdf57233f19fe9d9ef165f08161807a8b3e643021469d798fcd716bcbb4dc042b96e58706040d5a272a3f40d429bb772f3f77933244f690c84ffe79eff80b41ebf2a9afadf24005919b1c10cf99807774e5d19fe41d53f18ef9a35e00a72319f67d93d72b9f713de1680907263aa583172f108da57691fccc4b9ffb010001 \\x889ee81515d876e4eb68f5780abdb9b1e9e26d810d3ee43c11f3a69d385233a78797dbe272626a9d2e6d95f6f5796d7a31c27b95101f6f41f46040600dd12c0c 1652260407000000 1652865207000000 1715937207000000 1810545207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-358 \\x522fd2cfac3c5f27613d9861d3973f2afda56a629ace1513261d27fb79dcdf52a21f6347d1555e5a77b17645aa0d8aee3c8e3ee08091c278fa41e964f79cb359 1 0 \\x000000010000000000800003d12aca62606de4ce58653bad7db67e54004090be5ce8c372c1a46417e668655d2f32118193bffcffc090a0b50a2b79369147a49412f79ff0b3cba215cd9f634a93e61a2baafb00a6728683fbbcf1121cba0b6de47ec7cf39c86a0c7ed949a5c46fd6442946d2843fdbdfc3c69c7d7fa6a4c7da060ec8229c4cb5afb8b887438f010001 \\x9bbe19234be37d99fe1e2d3b5c02346c3bc77aa4ad49287225f09aa2173e32de6e2853ba2b0ab08a628d5b74350022a82c9eaf900c2b69042fb6150811fa5504 1667977407000000 1668582207000000 1731654207000000 1826262207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-359 \\x535b71f67a6bd7155d124795693f79a887f13169cebfa9db0539935d5f1c4e5d773328007e2f86f5a660e7b3e150ba50ba423696c25808de39c73b8943e1fd27 1 0 \\x000000010000000000800003e43b88a93609747883c6d22b7a86aab575198ab1886a749cc3a3c7e92df5db523f75ff6d9ad09e5c5be5300af420deab9db24cbf83ba4dd0b27d58c4a0100872a432504f5ec9ddc072132834ebb568adc237e29c9c4df897347b96668e6b37b12535bc0d7313f8b2ac03e0bf719ff76413532d59021be670106c371fec7ae10d010001 \\xac1fe8ba2f9e81e629154cdd0e397d4a9e10e406dc55e1d4bcff06e500d677f83636df374a8f12abfb3e0757419f9d7f63b93ac82e68398a52b3c7e54fd3ac00 1652864907000000 1653469707000000 1716541707000000 1811149707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-360 \\x560b99f079d078f81b60b047c7f27f42048a8389ae967f0ae51c48d6d43d1d8acc7624bb8c3b477beedcfeeb1807a00a8651c2280eb04f1efdd42787fbce505b 1 0 \\x000000010000000000800003dba8709f1f798175797ddeea3081b95e9b9efc68fa4a92b3d47c842da3359060f3cb1302d1523b228cc089d5172a538aec7d833a5042f69787ff29652c05a0400dca5b77cf07bd4accd4bdfba224077d27598731e10fa9d9deefad2650db2920b82ff5ad1e9fc29fd92045dec91162b47160bd32c3868c2fee25b21261918f1d010001 \\xa8ff30cf84362fcd9345d79fe08a60cf2f4c3ee0310602df229028fab0b585e68c983ec04c6dc73ede9827d5d6bad18a176bf9521e834006d7d283ddeea17001 1658305407000000 1658910207000000 1721982207000000 1816590207000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-361 \\x5d0fdecd6fe79d6e64d19e97275983dc97f88258423f90559681cdc1273e958ac7ba0f7a1aeb22db233b25d67b2c06ccbc909ccc71f53334c9bd517481518902 1 0 \\x0000000100000000008000039f4270cc8fe195b07de8e73101e88a4528462afd2e37ccc4ccbb5e2c541cd4e7cf14ebae709cce9c7b5d75ad43fd93b836dabeeeb2eb8f6485389f7268e9319542945e229ebb8da905759c2b28edab8ec67b042750d9df5ed87f86037da096a18115fd29348890c261b6548ed74301eccd85edf354ea31337fbd9e5216373d1f010001 \\xdd01020ad3654dbc6b3b48cd81d9473396676febf020b955a6878c7776351e08d2d1ec9f537db80c953ec2145cf81c5fe1772c7494fd1adec2aee7aebba5ec00 1648633407000000 1649238207000000 1712310207000000 1806918207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-362 \\x5eb34af1bbd39f4fdbfd4b8e3cf70f7cb625759d97bdb80e8f8736bc5093f63807cfbedadaf4a9c1f77122e3959d32b7418e6386638fda126d768d8dc1f97d81 1 0 \\x000000010000000000800003b6e2619d3e2491c926db9e61b2fa9111e5c5b574de7ea3009bc32d87178491fcb7e1d456df57ae4d1857fd02120ca13f6e4867b34bf9ddb8d2ddf6cf74bd594161a92412fd09809968380211c3d246cde97ef1f4e506e7b9bb5fcc50b1ad9f228a596c11ef51c23804bf1a97ac2ead3103c4a84e58c552f3788346de62a324d3010001 \\x6d39c69908e89e23e2abab1eb1d513bc7ff51627deedc816e1dc7127f990a99d7df00d2bc2769f46f9a533d9b26eb12a9ab8bd4c54e0977646abd92b8bab3606 1669186407000000 1669791207000000 1732863207000000 1827471207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-363 \\x63f78cbb81ef3024443a852e2995441359bea505c1b742d44bc84e12c42bc3498d27370e8de467bde6ddfef8b6fd7e0e82027ee68428ba7214d991ee1a09abb4 1 0 \\x000000010000000000800003b15d4f5000b97f820186546ab62236b4842eb3b1fa5b9239743beb6a45e2f7b997642bfac8e629ab9d2a42aaaf33b2fcd1efb1288fb40afb6528cf377c351c2051125339d210b72dcf3c58855762f66ebc0d25513a82a294eab8c7ba04296b8cb27b1ee19462b080c628f8596d99729efd21418abcbfe1c8342c002c9d163a15010001 \\x855ad3b93312fdfaeb9cb7810863ca065d5f24589ec29c4031849f650bd30e94549b750b3a5ad5875b5dd0c3c5cf15f18df9007c88b35510c41e4b5b22acc90b 1664350407000000 1664955207000000 1728027207000000 1822635207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-364 \\x65efcc57d9de067e466ed49201fedf574951967300cfd5854013b31b34749f46b1e3771b10ab1541500c060292a739ef367aebd23a3032455cf6522aaab2dd60 1 0 \\x000000010000000000800003c8d56c7b3376379114edfd303aa03e7a5dc694b91c761f7b3171b232377ce600e80eed8f1e22aa7b6ceaa5693ac1fbdbd4f1d1f17f18be08fd013ba0869db60e1087d31f72dd2df2e9156fc5db842f70c2376f7d2eb748882c206080ea6e6f1faa606074755526d3bce7750485edf99d7c0551ecd49eb7410f8680ab9aff080b010001 \\xfc015605877a987727b4eb69f071a84653a7f63d9e929f1c007cfc1f2789a92a379e207b5d537bbb80fb09019ead8e3ca3d5fa11fa204e21a00dd232731c7b05 1645006407000000 1645611207000000 1708683207000000 1803291207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-365 \\x66c7cc9481a5b81957cb80bc18ed5edd1914a24d5d4c75e433dfebea26e3a4806cc4d2a1997b929e2cbbcc2219b7bcc370cad01d6f76e6c5f6257a5a777d2731 1 0 \\x000000010000000000800003e6ff68a75222c9484ea024d3f160d9dd2d6b2f38d321c4ec48787e161331b3885ee368de9cdaccedb442d8e6e80e12b7314573ba2e1dd816a3e3b5db2c61ab0d2c027ba90766292beed2c7faa5a0f19d3d238d1bded9081607ec15e4e7f12c0b4309c848dbb108da317a4699abd1a59a18377699061da57cce0156bc74273d63010001 \\xe9f990f1a85fafbc46110c4ee35f79f5ba42bb294e150004cb8c7071dfd3c31ffe39fda158d6f052e98e52a5da09d1ab41ee1d24897ba86b0d4d9cb79b63c200 1658305407000000 1658910207000000 1721982207000000 1816590207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-366 \\x6f2feb7bb007c5dac58927decd58680ce6946daf1d34d727939672b36b8ae34456e28899971f111db74cf7b6d60405d84aa8e07bbd783c108d31430a6e41bdcf 1 0 \\x000000010000000000800003eebaa0e46fc36cbf56b7428d62481620306aee225407daf1716d07ca69a14fabaaba8a44b5fb89d95470162bdd57b733e743f592a8e863e0b9b7d7d35642ca413b470123087852fa23f12c78bdbb7c069bf57ee210cf381f522e186df589d0d8692dfa0e9e6323fa51e69bcac98adbc4c86b5256967033671453f01680565765010001 \\xf16322dc3f1536a9cbac4f83c735b27bc28e01bce31720fd428baf05454a0e738007ad1de5729e8ad1c784ef551613586fbbd32cfcae390ca2dbabafaddedf04 1646819907000000 1647424707000000 1710496707000000 1805104707000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-367 \\x72ffd191ad2c26163a97fb32652bad4b4efad03a4da2c53506b75ba64fd6feccbf42eb213ef0421cf1a35dc62f57666b72832115fe23fbc871e91c1b937aec1f 1 0 \\x000000010000000000800003d549df5bfe9ab880199a11cc926d003a6073b8b251e6bdf44bd4067b70c3e54c528fd817648ca013032f75f637c4f9c6f04eabbfa8fb576dd10a47756d090c3d8cb305ade0054a8b9c97e13a6e8289c33100ea1d8235cda871646e7984fd1fa8e90680fccb25860d9c0c5e9a85685a1801a5dc8eb69990f1686c2f01d79b4d83010001 \\xdfa306fcbcf57bf9da9514334989ffd1c2e01b5f96c21020de76899a63f50dc8cfefd926e1c2659e738f84d636d63af073b599fc78075f8a4feafe45f69e8b06 1660118907000000 1660723707000000 1723795707000000 1818403707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-368 \\x77237d295d8bf9ff02082898d15cc8f409de908e247dae2d51c9b7a0271ee21769cae1e2be0c497b2844254aa408658066df3d27b431a1597bc4a7afd60f0d6e 1 0 \\x000000010000000000800003cb46e1ee4292e84470cb5ff6bd8a48017b51b6bb4995aa2ff896f4cdbb5eb096f00132e7ca2ec9f62f2639a1ef55fda926de007c32754fbcc3cc43146e7a23d01dcde7baa82a2f5fcf4381615f20cbd36509623758c0f997410e8b165582521423a1e29d3f0eb94bab052e2091a84efa12cb302b81a6151955e9248aa80ccc9b010001 \\x676ac50717cc0655893373ada8bac946caed9d22387d8575a6d1a9d43879bab8844e35a1c60fa7131ba1b2725a914b67436e7a5fd344712d270d9298e4fa9c0c 1638356907000000 1638961707000000 1702033707000000 1796641707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-369 \\x79a3527ccd49ed19548636934fae0f1da806ca7bfeda2c18e02ca14870e2a477b765ca1948b4e1c4265771b645ec93985efc6b287ffc510f46ba7d91b0d3a931 1 0 \\x000000010000000000800003db2c57f08163688cd408025e8ecf984c3d29aafece1b8b36e26c28270cdaff95ce05cceb5d0b0125385e0ada8e91a27ddc52b356612d4c9d75cd9b353bc7faf5ad99d9b878e1dd16ea88647120c0a16375a17df37d5790e1319e08a2b0923134876eb87e5111c75ddbd4a47ceea243f76ea6e1b3c60c8eefdcc602d7f294a205010001 \\x2d776c44781224eec988ab35d9ca4e462bd3597729320649e8d1fa042b07ea28576e747c220446a7bd014c777c70909f236a08aa666374aeb3b814ad589fed07 1657096407000000 1657701207000000 1720773207000000 1815381207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-370 \\x7a5302518ab4906eab11ecf7864015cfc7622001a8dff66f87b03a3df1eb8f5e680a5f0d00f0761e4cae574b13b75d92cbaade7582ef9ba43fa7db254d3fb1a8 1 0 \\x000000010000000000800003df9d094a0d86e424e3b40840e9d54db7f44631eb8b2824a1043fde40434e94ebfac2cbf37353667d329868a721db47eaf069a1713b6942dad242d1d10abecbb6dce87a3b45dbc7308e058849fe08eb590306a459fea69eb8be92ab87f247ddf915bd12ce2c5354cc8ee876ebd7539d656acd8607affacb871fd2bf0baefabc5d010001 \\x91d1d1f6960f8f78496641f490d8fff86a779c151c14f013d7e50ceba3e19a96886fe73a83275e457bca34c05fefb50dd2978ec05da69c014809e34f5dfc1b0d 1660118907000000 1660723707000000 1723795707000000 1818403707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-371 \\x7b27f0925e0fcfa589c92248a95caccb1f7c779cd27f41e0d90f9d0ce82aa09fbcafdbdf5b188e2d9ea918d24b8b0149fecff80d03c7c4abbbd02fa4bf5a28b5 1 0 \\x000000010000000000800003d4acea9bd67a584a95f8e755355c8fbd75fdc8889d6ade8bd01436a3b32d7187be5726eaca29c71301f18b6e0ba74219907e41fabc5a2f98035c41e4da527ab5db538cf9618288b6f2e70af78b7b205eb9519eae12030c94814048ae0b9269e266439bd1dd7f8932fdb0c8e1831b5d0496247c1d787d8d7b2f97aa92d6a01641010001 \\x5bb3aba12fb69160bb83b868bea73c317fd96af6bbf34d39576ff09b0a8fde2d4d5f5633e9374eecc3a3eaa6fcc178565204d48e289a2e1e9786802d6f472406 1651051407000000 1651656207000000 1714728207000000 1809336207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-372 \\x86ff1481296384128d4f4810ed0c7413fa6cf02a90f21a0f9a21809aadea89890b2487c166b49caa74bd9d57ca4224ae8ebf409380a69b20166ad331c792e17e 1 0 \\x000000010000000000800003d071dca4efaa4eef8967ea1452293fe90769ada3ed73d30f1ad10c91f6bb90c75453241c401723b2810696c3934bc93f29ec856ce75ce4a800b44e6d8871360d26a90246f449e254374246d30e008e921b578e0ff79aa6e34d4d82dd5675dccb3f7b3ffe13c78ab656f45f845c2b47b5f7e8cefe25bcace6174b9c343b515ad1010001 \\x9119c7595e3670721e1c5ca24b6330fdcfa180a04e045f9e98d56b34aadc9c8e4546c4b582d469451682441de6cdcea3cd8137fb522787acb695958ea03ee80d 1658305407000000 1658910207000000 1721982207000000 1816590207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-373 \\x87eb9b3d9a314ea975107e0d3d0fa7435bdbf1cedff99aafa8a1d1aaf8f106e3832ca05644456564a840128a84a8c00e52572c72dead6e8745600390f609ede1 1 0 \\x000000010000000000800003c8a076dd9f0d866ed368b65bdec0f78c8b468b9e4584c632f596266a9ff70695ec727d9474a2163d45f43582dff7ecb02b6cc2062d3c516ab4f96f99528c51897cd802f7019ee802646dd18edd9a85bc6ffc78d7b43df9b264e770875fcbc1b58775c684d62038df28dcd32904f186fc52024b0fb5e08a1b403e196faea34257010001 \\xd9fe35308d7256538eb62acaafe931e6754e6b9043a37c9528e8953d8b98ff23e442055230ac3032661d1bd6915e9748feb1f35283ec2a08d8425e15ab3a4107 1640774907000000 1641379707000000 1704451707000000 1799059707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-374 \\x8acbfa4c3d3d35b591689431706a2d54ca7431751292d4210a8d31ffe0fc3c93ac78809f715b0d4d5d90425f844d57548958baf6853c8d145aea276f579cffa0 1 0 \\x000000010000000000800003da81693f32ced4ce97c18c644b508cd29e0958f09f545886c18d777e72507d1a57de13b38984be4500cf622d7c3496c5cf953f11fb53e984c8d71d87f74df9b5d62bd69c363cb15399ab153d91e3bf81c03b708f923231c9a6722b24b8d25b8093dd3dfa269c16e3a90791018a600aba8ee61b2455a121fa3dc693ede473e4d7010001 \\xf1a82df65d6ae836e990fe56f19ace1a04572d735981c4b978077ed72cc24c58fa37c95c06d609881afe0dc1f25cb2e91a5216aed4f24617509eba38d3a49900 1648028907000000 1648633707000000 1711705707000000 1806313707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-375 \\x8bd3dc16f35017666b22977f26991656625bdaab29d57511b0379e23a539ceff590de64ec06401d05e75f55a4b450c484a479266a3646b896c18bd897d6a365f 1 0 \\x000000010000000000800003aeba3c1478f37319610c94f8183daa4cf230c1e2de70312e99a6116990862c834a26322594007d320f1e688b59684a7456b18b012015c6517abe65c01b1b30dc3d606b1ab62f1740fc5f25b4b323c8db17e60e25f5e3ef35ddd7a30c8ff6e815c2fc4caffc2558e6dddb7cd8244263b7ef1cad1ecfb152f99fbeafd5c129d3f1010001 \\x1a50387c04dd65d41737117279969c506a3e3ab91d243411e20348c5cf11b5da584dec6d187899d63f00825c8ff5d4acbad11eab7bbb29abc6f0d43c14e81109 1655887407000000 1656492207000000 1719564207000000 1814172207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-376 \\x8fe3aa7bd51f07c1ddcbad6a5db43b44a64652cbe4af9cc984897235371018457e34f1c9200d79f0d59b1f16f5dba728549503de2b3af4a7bb0064cf84164282 1 0 \\x000000010000000000800003ad13f7ad2199fbef172b4c56266bebd9f32bf8fcb5a43eb2cbf1e62709726d6ef7ac5dce6960461eb2f4c3d9cb71289a28df4994276e4960055ab21947db81edaa22c0f8a68fb78ba6e841a50152e44ff5fe6d3f5b979a0e99ae57e9020f2483f7b90f2f4de547155cc7e1f517e220c3e0e6b5fe947f1e94d578443f0a50df6b010001 \\x0b44a171b9cfb9933dfd504b2ff426fdd36ee88b30274f383eb18aec682088ef26dd6fd9582344f6738f308f726d75597d13aa948795821b7e91e5141955d208 1649237907000000 1649842707000000 1712914707000000 1807522707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-377 \\x9617dfc32fbfbc9bdf40975b7852974ebe4dec067c0b5999cd385cd9c6c54cf410bcb69516f37fc6b705d541b88305997a5b8c97d7f08e50f53bb7eab695b8d4 1 0 \\x000000010000000000800003ad6f289ee36e6fd287dde16f1047710d445044d3e4214b3b0ca43c0ae73a6313b6b54ebed875916470439ba65e879db2ced833fb72c91a95560ca8b3032cdf350b31e44631a192a6db09125fd3243708ce291383365d1ffbb3816df58e3bfc00d6a91542ce7f8ebbfc49c1a102644cf1919456d755cc67a84d8e40a39a5f6f29010001 \\x39fe8a5bc8cecdddf0e9da73b618d165915a33c29fd2908114b686c67e49d44ef39564886133a27130b4ff2bee180e91cbef59735e358a10f997027bbfdccb0f 1656491907000000 1657096707000000 1720168707000000 1814776707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-378 \\x99ff5f5ee3a999acccf33a08c305951b4d52b04333125be26775e16bef65509c42e5adf09d4d66398e3de6d184a28defe890054719a0f5af07ded3a3f1fd1a53 1 0 \\x000000010000000000800003a8ebff8f4d39c31e5b1094a2fd8d7c78af9f98a6438298e2bde89927be55c6995baf8cf65766bc34ca3555ba4c18803373871016614cb2dda57859d327cd3f618c19e9aca0f329f07fc7764bd93995364a8ce9ef7661915c1863b7451b10c2cf304d98c41c662afd79ba758aa792c2795335be747d2d58ee90249dd4f596cf0b010001 \\x244030c4523d52bdadf69dfc22f1fdb44e98cd32d71780a67d1905cebac74419b4c9420f22704835dc7df78d908be4da6f06a905fd96a2db6fd052e78605d208 1658909907000000 1659514707000000 1722586707000000 1817194707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-379 \\x990f504e84b4750c746af47a926c13c7897971b59ba16c8e4fa4a404d138038c7efb52763e56fe4ddf8445c9604674cf2b354a10db5816c01ec0d1a08a77464d 1 0 \\x000000010000000000800003e5d42c50a33a51df1387a2efe1ffbf3987e7f2d76fedbfd5f0096002bfaf6328ecfa7ff45723bf96bc6c6e770247d0d071ffe05e2c799a4048fb2a5b55814b4a01df40aa7c3ac42f2491349bbd52d802fd49c95414c65a808356968a4ac561f5f532a499fa77a2e2d34bf4af6b381e297b1eeace3b9026e9f5e16ea5341c03ab010001 \\xfa378a8f88d9a87ff17da52d0698e170ae7fc9adfe8eca65f117ff9f2c8f6da41daa8482ccbfd502f681b049e8ad57db638fb0287b58e0c9346025fc5370ce05 1651655907000000 1652260707000000 1715332707000000 1809940707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-380 \\xa2fbf6e343bfb89bde8e1f743c1cd542cc0ce4d0dc605e6cedc3b256711a8f3fde994a780440bdf513485897139eef7c694ce374d8ee91c3172be806e28a3a4e 1 0 \\x000000010000000000800003bcb1db30678cc17baaf20d22ff003d54a2148302f6a9f07c26692d5551b2928b61d62c20f7b9a7eebc8728a7f46da0a69cf073f6ba4a6064f8500ca14fe6549d264c50cbffc4d262f171f0ba5635913bf3dbe52a0cb18577999f25dafd816718ce5b2ce417e086dc935836e0fef212c204e3dcaa77c0a4d9d8aab45112efdc67010001 \\x0f950425b502d1d7c7633686c268ff299d2a9364e19a0720ca168755a9622c1dd02bdf60fca3f75a4378644de1730a6e164a6471557d0b588edbaf10ca303800 1669186407000000 1669791207000000 1732863207000000 1827471207000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-381 \\xa437c99908dfbe0074ef878005f31466f31ada86566b817d9da73b1d8b4a60510bdc72e4cbaa2e645b85ee122da34ce05dc6384e76c37d4fab7fcbd4665b830b 1 0 \\x000000010000000000800003d000282d6fd43ec97c1111b29d892b27b3e48dd14d506527d007d69b587ba2065c5e2c58b4a0b635d41c305e51d0f1bf8fca4275c1c22d3a8c953298676f720ebca6fa5768b093232613d0b8f4d15786690a996f9e8e142640953b92ffdc455b644a22fe24cb597163d28962e2f7265bad213c1ff59f8f051aaa23ea400e4e7f010001 \\x7afb6b33db15a35285ac29a8c97627d296c91447a4823cff5192607910d368d6e73701232208e812bb03cb5001eb1c7f9c67ffed9f9d46232ba00d7942335203 1669790907000000 1670395707000000 1733467707000000 1828075707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-382 \\xa78fe5edbdb832739a21a2d32d1569c0f21cb4fc2b389cd4482e7d6cb80115fae88b1bbf5a72ca59ff39aa586c134a82cc3db785147e4cb58bda5225452172da 1 0 \\x000000010000000000800003ef0fec1f3d5294687b7f424f2a1c8c515c2cdce3566431e4a309d8fdb89227c8a8d0c7c8b471b93904e71143a5a8e2e43da0f9abd01af144ddb5af04b29228e9d2aabc44a995716ab6e27e2bc649b36f52d40624a82f713862f5fcba89eb6d25586a9fbf1bfe4caa793a8b943842744d8148e01300dbe22c0010580e16dfd49b010001 \\x391ef1ce4a76808c0efb29ae0bdea20f938b7c67459aad1c112cc31f91b377e572ccfbf68ae60168a45a0493bfab47f4f24b1f601351b9657c725dee7b5c1a0f 1668581907000000 1669186707000000 1732258707000000 1826866707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-383 \\xa94f458b7ae38750af48dde34daa49cd9b61d5699c8ad9aeec9f6698e47205bafdad6a8e956fc20fcacc37a79eca8f4378d2d2a8b569948d1c9d3e003e184022 1 0 \\x0000000100000000008000039bf5703084050d5b104f15cbe040b08f7779390746544ba02a0bd5e54e88aa970c4190819892c1d457b2f90062939a57f76cbc819bc8d2924882e7f8073816c493ca3542d335da201f7721b9b3c2cf0e5c07f10fc190f63a27da2178d65ca113c230847cee75135696fe14d25f277f4e62a1b56860b9ba27b2572586f8de0511010001 \\xa4840e8db9671e4777162730b59b3a4f5cc4ed8a0fee767b5e35344302d9066ea892d09d6a82b19cf03409c7e5b4af142fdd633443f2561f3cc3193ef795ae03 1652864907000000 1653469707000000 1716541707000000 1811149707000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-384 \\xa99f1cfdfc0a5cbe36be01ed9395975f540d4a87131f1f9c00ac76293256125478398a490e1110ddb0ebbc9d5ffa03b05928cc4199d655546b27b9eacc0f8ad7 1 0 \\x000000010000000000800003c3a13a0a42f66d7891ba74ebaa8470f84728214309be52d2a15cf32f7e8343ab72d47eae83e4aba052e12bf33b734541bf772a01147f9fb95dacf12c4a480179ff1d48778eb3b6da91d0b3084055ba7e7afceb54138acec19c73a9e5712c2aad2c21a2881402ba3451b067b3303fe7328a21dbc7e73a7613115ca2e6d3cc2671010001 \\x7646e5269de58bdaa7c8a1c9e7039449770505d4ea4a2330bffc23fb5ffe5f061028830f37949ff15b1038bde0a3324a829770c84e98c20179687a0ab6bbe508 1660723407000000 1661328207000000 1724400207000000 1819008207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-385 \\xafa7e1117a99af973af20fe6d33766eb8b779ffa1d20ada900ae98e8511bbb07810f59026740d3a19aa305497e5f40bea09770a6d111156297d7266e514522ad 1 0 \\x00000001000000000080000398d7168d30c2a87b711bbc900a8ca81686a0481564d169ed4820d533dfaa4553e36f4dba4922551062f23319e1c1612215c9f6dd8cbe04c7053207a44e79024b114fa9fe5ea3e5a254d9e8cd1fe9ace965f9306c068265745e690da2bba5bb0be280a5d44fb2eb274604d687872ad1c7cee70a387c7c2f93854e76110a07c073010001 \\xefbe1cc96469ffd5b12f49ac5da9e12471dacd72516deb6dc8053da3711a1be6b5687d88466b5fa44fd51e148f6050d34e8ea04292b7ab86472029a8eae15d0d 1657700907000000 1658305707000000 1721377707000000 1815985707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-386 \\xb14b20cec071b2afa387faa2c48d17b59b799c46857297608767a89210cd70f66a3a2f549d56224050f86b747759d3717f34df47a50e57ab28f54c5587adbf51 1 0 \\x000000010000000000800003ba13da38fe00fbaa81523a07a025b8d77898d54cfe15a688b68806ac5e5e4dca74630fab69140570fc26a1c6765eaf0e5cbb4dd699cebcdaffe1e3899d59844025d01e89d3cf597e7c2cf48291cc406cf18c4df78955bdaedbf601ac0cc3276a1fc1f42e346cb0b844094fd29646c94e4ebd480861c5d535a197f4fa2dd067c7010001 \\x6ca06964d86635557421f7ba0b04f4a0805560b01eb4e5c8bbb6920aa7c1487feb9824230f983fcd15f976c7b7e90191ae3afbeade95a4be0b797142ea88570f 1654073907000000 1654678707000000 1717750707000000 1812358707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-387 \\xb2b327c878f779ff09f1f47fe9c24aff40fe8938fa2ef2418cafd4ec8adaa0fe2e814cb9654b7c86bc5a626652eccf9dd983726a2ccdf42d161ebf651a9464c7 1 0 \\x000000010000000000800003d6badbcfd2f45a3160641a3c76b839971318cc08b15f8a1160f74a375ee6d47be4535c1b48f90e8e48dc957c93d116c85ab4af5e6c650515d98a100bb1e1ce128039c9a313d0735412150fcd4dd055ccce2ca304ca77606f70d102b7c364166546a265d885e29a77e43927bcc499190022b2beafd49413a4f1dce6f3b66df3c1010001 \\x3045a38516160f7dc5d35bc2fb1581447ef7d95a45db47fc89059e8a50ebce7efa813145bd8390543b79815b3289ec43f5cadd688ddf27c5cff05d0b7e165d00 1648633407000000 1649238207000000 1712310207000000 1806918207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-388 \\xb5a753a28125213d0d051f652e20ec6720ca19e3e02d4fc21556760a3215d3c61cee22b4f6ec07bcde41c45bab3f74fecb8fe7b761cb7e7e5bb3c35015333a80 1 0 \\x000000010000000000800003ac5c22aa66a0e30d662d86fcb41ff3f810fab78cf079d9f817fd2422289d0de4682173b46ce5058b3f73ee5e46791b71e34a11d6b4305311e1c52c4b03644a6160b47e9ccbebf6a336e50d52eb456f77c720190589c3ddb996b3c022dc50060e12e4d9507a035c9bd512b25cc87fd7066f7ff9950de34426f3190db3e605a96d010001 \\xb5183dc7fccf7e019b86a1e85dd09fafab0475f115dba85d07a75fe876861a65f3d6a5fbbb3537681264772b7abdfea00845872cf265433db5d09f5a60f5370c 1647424407000000 1648029207000000 1711101207000000 1805709207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-389 \\xb7ff80b0a2ddbbce801e67ef0e0e5001eb0e784f45a7e15aed999b075f31dac93ec2afabb75752d777977e59c18251b745d3fb8f6320e19cd7609434b0b3d354 1 0 \\x000000010000000000800003e00f8e8cfd287175310df669e1216ea6980192cd73a38cad0c397946659033cb415d8cf64068c2ecdb67ddf3edd84345a67248515d298bc25e3095964babd54a5c035d0dcefc1b6652f4922e1d2bcfcf38a91ce936af4b24f656b681d60e5bbae0b816a9ee681e2de73af8c835bca0310ec4674b9ca135e0d986cf0b0e93cc11010001 \\x2818080fdacb3c80856c4327fa1c1924546ca0f969ed93c99c020f286df997ed5fec4e5be7570686b6bdae90982fb131460b9e905208388ca8d6b979f6603d0f 1668581907000000 1669186707000000 1732258707000000 1826866707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-390 \\xbb2b81d485dd5d6a74c4afe00dac5dc6a0895a32e916e386b60cd5509f69c234f89db124a5a2af3826d22b50010497ab43922bbe709732cf229ab3e19be40d87 1 0 \\x000000010000000000800003f05db0b491304da6867c398700f54618ca28dc13b4153878abdb2cbe7bbd4ef2f1b57a8730877bb5b752c54859b0e264079c303bf056a34539b828b3f65827d4c56f30ce9bad98e0f3994580398f438fd152ada0ea4a306a9650a3cf7a51de503d53c37b710e2520f395f9d2e93123f2ad661f7d247a99d9ce54292565d8fdd7010001 \\xaeb100eba69c9b8bdf2f72b7b32bc9affc201832af94c5de8f89eb04749ef1371c8ce4602104da9b8c465bcaa56cfa38dedb20624defb70dd97e326f111bb30d 1663141407000000 1663746207000000 1726818207000000 1821426207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-391 \\xbb4fd1867b0e99289df037213a839c92ce52e262f59b33aea5608e91be199b5a749b65df5442cf1b71d0878c75b7737d8d8316192dcb175ad2e5e62b68557d87 1 0 \\x000000010000000000800003d704f7c16d9bd9fb963ba18447e05a95ab1926424511746905c0401d5af29ff829bec522c801da8c901279beb78805d59568cf1e885d7840ca1e62c92e271be4afa95740ab72c533339859a4ad81a4f91050a609d2371a3f0aac71d6b889f1a95a4fde4d62a007ef65fbcbff56c9d829b72ad7f81714a5a421234a3da16c8775010001 \\x60e159e72654f2d18c9e84b37ca16925af19dd0a47462d0570448793a8aef8a95a49dc1531d20823d7f20a611d08c7f9c09d1e78e6b79974567c13e09388c80c 1639565907000000 1640170707000000 1703242707000000 1797850707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-392 \\xbdbb93f47041ff7a8b490a2b1fb55282235bda5773f200fd7d4a56e8a54a2e1c34d15fc2a140136cab7253c359f0792786077f86b7d38a8478c9d3d9d6653922 1 0 \\x000000010000000000800003f07c198b5a24daf91ff6c5a3b5939eae9f6f379d3ddd6fdc48d9c1d2298ff4ad7f09dfa66ddadb9bf67088a15ab7c1f2a3b6136685f44e2ae8a14dcd6ab77ec70f1f8abf2de2815210ab2be6a7410411997f0fe8bc8f32c0f062b4c2474a20898c4c845635da8835da9f7cbf88a44e7ba41dd72035a86a2cdb8d66040d0c64c5010001 \\xcb1f79c8c001321e74f8990487e6fa697dd7a7c058e2c9bf01997442bfa223acddf6337f7e580a587253113990952237af16ba3c0d244438e2abb7a878eb2104 1640774907000000 1641379707000000 1704451707000000 1799059707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-393 \\xbfc33d0c4d8ff750ada338ae1b0ca0ab95af7f13f80360683d38683e15ff32822eb377da9f722b385b974bad49a84f224b115b6020cdbabb522dd61e69fde0c1 1 0 \\x000000010000000000800003ee86ddf546f47e3a6ebc0a80cc98fe5ebfa8885ee73f9fddf104eda9ecf329600a4c7c3dc45123840c74ed52b87e42e2803a9607274c8eab82896334c8ba3a1118eab4a8955f8fc279e78b0f9d2d2ece638b127ce3c1179c5e06e52b7046ddcf1374c73d8078b83f59709c819683d5064c84a09621417fb12aefcaebd5645237010001 \\x22747b859750d745851ee20f90398cb8b923ce750f9d643293117c0ed26e0a0ee71140fd42b43ac62717419db64f969bc2c9ffd872fabddfeacba404e1563108 1648028907000000 1648633707000000 1711705707000000 1806313707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-394 \\xc2c3af5a697fcab0ca39d06eda9ee668dab198a3b60a35ef770e7dfee37be1e354b6f72ae85f5dc0fe384fd7ef072cabdc97b253f9ba9f81ba075a48b54fd53f 1 0 \\x000000010000000000800003c4745ec7b139207783eb5b61e9b9bf75ddc66f678a4b07c606be631dcbdc30b963e2fe921fd3465d7e4b822d7e26b4e9199c431a93ea2d9c8cf00eaf82aa90a703213965f2e5fcf09ce93a5517b20ecd16786cb48972340aabe13216469b72a107a73edcd51f3414285764b4f9ae491e2870884e7fee9d026540d2b635dcbd99010001 \\x3f31490e541681f2c43aef291711571584a477f19e2577bfeda66f6ec16d06f784e2091442d23d9ff1dc20f120e76180b34212a3db31d45f1892f02f74957f0f 1640170407000000 1640775207000000 1703847207000000 1798455207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-395 \\xc99b04800ab16979f15fda289b6f2ee2f3b6f349fb594b9fa4536879329566ab7657b66699f2e93a7bb035302e7f35f6b64bc736c0189f50618084ecb0daf931 1 0 \\x0000000100000000008000039d4fa580a2d9eef33e18915f38029641b39aafe73b5b5912ea1445b95f7259737c3d562237e239493f1b71aa72174c8bfdf3acc56a48b656d7f44c968d9ac2503dc47b75d5ad9b036eb76659a5013e84f0d7acf5c4c0bc18079e32197d6eed85cb4cbae584e5e9f2d2600c3cbddfb2bce0f9ba04c77b851d99383a9cfa78ac47010001 \\x19edf26f814b5dac84628e4fb8fbca6e8940c66ad9f37a9f356c2bb422ddf0b5c42e7af8397c6b8a1c016af976a8bb8a205531a8b45ff48937dc7c32caa13e07 1669790907000000 1670395707000000 1733467707000000 1828075707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-396 \\xccc3ccda35d855917f602d86381de6106bb75ca74c2244529f96bb1481b460e9b2994defc206976e3b39fa170b62f617b935dea74e2c2934fe8baace5e9b8f0c 1 0 \\x000000010000000000800003b455bbc39fff26f5c5e12f7b2705a9a54805e96fae7091b543af91a5593d71621d2edf7b49805879a5b626aac9984f073310ff8197b0ea2b667921003ed1fe88bcc7d3eb6a691234ba10bec69b79d60eb70db68cc148e04b226b6adfad445f83930002041df89691de61a855304de48516a2ecfe0b91bbc4412d1a004bfb3d71010001 \\xeced69a6e3c947c9194dfd3e52b09114aa1bc9107dc289a8485ca9ac114a562bc1bf97f74a2d4b1b5d82aafde9b85c87e4f3ae2f62b7a351635fbedd4c48ff07 1656491907000000 1657096707000000 1720168707000000 1814776707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-397 \\xcd5f6694b1cd0242bd2dc0fb7b0713a8d6a93556d359a3972b939587fbf48940f2852bf2a6d06271870e932cfbcca52c358eb5f7b060005a7818a9a383649c49 1 0 \\x000000010000000000800003c2df16a5dc66227e302bc661d5f590315a258b2953c4689f9e22b0f768970c97d27e9f23b8062692f4fc7974a2afa068a8c0dbd2617bf85d2b66da19ce34ce6f45ae3ae636975c5b1562178f5b152e2948228bc0704488bafb7b764b6d423fab0511abb34b152fc9321b1420acc89be2c92d8eccee9b1f14a7b18b8a9028f63f010001 \\x2bb5f85eb2082967f14e221ced11667bf6d00ffe9e1102aa8004a55d5b8a41ebf88d099b4f4e887fc99369822b1b77986ef8f6301d18562f2bfafd98a5c5980c 1649842407000000 1650447207000000 1713519207000000 1808127207000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-398 \\xcdd7decca0ef3d431b5f0c83e13fc817d2c0a882f4fb024421de8469fda9aed200ab4f325f94138a6114fc1e214d292aaf964ff9aca06ec0bce5aa18d41912b1 1 0 \\x000000010000000000800003d7078aab8d0db8bfaf151c09115804474197305354e590bd966a7f5ba3a50db3ca8de39aaaa7e2567937d0b274c2b14ec06d9b1c4c865222ec72852dcd6d2bfa03af04efadbc54802615560dc1134d30b6ac944e3259371283cd7c188bdf9f881e112fff91bd1a03132ea2ff8adad8f7e9a0c049ec6a138be7640b6965be7143010001 \\xd0eb8ad8e0303c9d8701e1212ae907f62cd47eb204f2c955c9efb76ea4c033b3e287c90252c335895ef523f865990e8af90c5682379ea6a4d3fdb2e019710505 1654678407000000 1655283207000000 1718355207000000 1812963207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-399 \\xcd135953b5cb1a1390353408c380af80e6a4e05e81b925171e4656eedc05b39db9f390a05737abe94f981afae014e5cd6c1ec785f9bf2a1aea74f91bb7fe3686 1 0 \\x000000010000000000800003ac92b4cad03d16cd82136f781a30c6972eda438d9f38461ef72a5f344652be5de8614b12c9ed6da2fb0b516d948c5edc491cf1724a757ec88c1263a446075e821de566828bd1334a7efadb5f3464bfe12e3879d0cc91ff970e299638ac4e513d39cb807cc773285d883a232bb53af8453452f9fb994cb92ce4f983a5dfc402b1010001 \\x4fb5dcd28a8c08f1afd4e2f155815617811074655fe5ee7e7ff337466890778dc4c5bc5bde9af706a802f07f4d58bfc14165045dc51d2c9eb31005576e5cf005 1661932407000000 1662537207000000 1725609207000000 1820217207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-400 \\xcd7f39133980fe514ceca61cfef32644414ffe1d7dcf4171713398c28f4f00baad48b4191c375e0cd9754597a94e4a3bcc8c84105e8a4ebde986013685ff74c3 1 0 \\x000000010000000000800003ba133edc518e885027b0d44935a79afed0c510556be79164094409be1ddf10598fba240853204ac130bf85ff881fdae33a53029487775ff252fa4c6c0779311c79b42300c48b926054b0998c20f064bf44718a0b0c6fdf48d7aa5a0a73dac3ec22f757d1dcb595ebc78419139cd45a8065450874a3247ab94725c44650f332ef010001 \\x8f143554b8384105eee95953a3ae7db58e79fb79523ec6c2ac2ac6e76390f3f242b979111c2d6d7037760071f1183327c6af7d88e90a5c5410ef6adba684f409 1655887407000000 1656492207000000 1719564207000000 1814172207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-401 \\xd06b5e3b47e340bd2d1df0e9e351d195e64a1dd7b9e68136a066eb36441418e848f0f1ecd3e6734637f5b6b2d31fead7ff0d4ddc23649399147068eda445af0f 1 0 \\x000000010000000000800003b946c02bb3dfa48742663b8fdc2893c25a24f175eff824da4638af345ec86f657dda453d2073eb86bbc8db3538afc3dc367c667a5125fb77c6a9ad696f32e159058cd31dc52baf072b951ee8c8f3cc02f3ec8172a0eccb94d6d334f7c6c265e9664ac56408e3459b7ffd14c44560c3378bba7d84d510f96658ca717b5f789e5b010001 \\xb436a88fcde97a00a8729b6bf0a5adfe1085ee2dafcaaff77983513b92e36a42303348db3a171c4c760c3dc8fb2992af94981e746c4e90ff45c8f3d932659702 1660723407000000 1661328207000000 1724400207000000 1819008207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-402 \\xd3e32493ee2535dc47b00a8b84fc94b86a6e7e978b51798e2d427d3b94205e12e908ec04e399b875d7ef62ecd3ee1d9e92466966440cd95d85b20ad78b51e32d 1 0 \\x000000010000000000800003cf3b4c19e1c394fc50f96ca420403ec4f20ac3d94bc2a5760410480522b49c525a80814894836f7ede904de7ca1541aae814c4c6a4659b9215bf2d4ed598195f2d7868b0c45d9b4f2cbae55d64d6100ceb41325f29fd475b42bc37ac9bc8de03453d238710aaa49c89131b94eb38e3e30fa3a825e9bfd065538d59c8364dec31010001 \\xf77741b28a9b62fa78617ba05882627ac61eb264ccc821d83a49f852b360f628f748a991db2694ef6623b5181f017fc8b93121d706dbe42dc19dfee1877eb80c 1644401907000000 1645006707000000 1708078707000000 1802686707000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-403 \\xd66bdc10d92f4709e7541fdf97cc3826105ec0a4f19907404d9b6208cb1d33ad52c4f96f67b04401e86de20548db21a36c3283b7bda2b7241391b1d5fa7b097c 1 0 \\x000000010000000000800003eed8cd91771ab3c6f75e06a6c78d5591c6d63517950c7bb09adbe2e104323710c6241018f43bd132b38cc18d0edc562b269d7ea765c43372bdbf1efcaa3c9266fdeb09f4c06110c9f03865fb32523038fff6d66767c2a2848fbd12d8f2694601aa26bb3916d9da9533c96b9b7d05a9cd239c4914cb14620089ab29ad840d8191010001 \\xb9fa42d686599bb182053477da14e3faf7ddd9d0159108e7690ef3fd8db9eb600372f75aefb65897020f522cbc8881c2d5312fd9f3c7c06659d78e637aee0708 1662536907000000 1663141707000000 1726213707000000 1820821707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-404 \\xd7970f0f2d92b9097a4b490a797ab837e261055ea9fedfac2fde5ebc9b460503008888c2d9b6b3cc9dfffa432338a1f8a0c6a98e9216e76491b2afee1827586e 1 0 \\x000000010000000000800003e28cdce3179d417a1cbe63180e353da12bec8b5bc1f212cee3302483f92e1eb6135ff3e45735a1a7d0a8e94f79f79cc16cc36395cc3852df17be91a67e827c73fe02d651f313ebf0389e0b49a9094528d41290519779c7da4a0b28217ff5dce16dd108ac93b064f578fdb6f10a55a4f3011dd7445ac7b17c3cea731d95aeeced010001 \\x3461f4bde099b1a651dca8064f5dfb26c0245c4e1e221cd5fd0682c120f7069852c17c3430f1f12b5cb43c98736ef65c4aeb0776d9bbc001834e35128b388400 1648028907000000 1648633707000000 1711705707000000 1806313707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-405 \\xd827cff00124a10f189b98975ed7fbb1eef20ff8d697832e9a49793d84627461a00275b4428e7688d706b1950e6ad7511b33c1ce98cc0b409174db1f4fd67f20 1 0 \\x000000010000000000800003f4b86788b2516dcbb7508f94129d06fd07212ebddc41e749284c689570ce672c5a47d640a3c19dee0d3a0692f3961cb7ef6efad191a32758c9fcff72c64f267cc79fcba1889a5757db16fdbad1b5e78c8ae69f2c34137a3ccf57966f923e5000d148c2dc0088461e878e3e56522e50a125e277f6b77a4afb0d3d7d17c820af39010001 \\x11d37241ea80057d93dd20b1f9efe22df440072ce26872988bb6c1adab73dc036544aa92508fc1919c8c5c35001b3ecf59ab36162054a9e0d54455b4e6c4d70e 1643192907000000 1643797707000000 1706869707000000 1801477707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-406 \\xdca7b4b9208f73f76797d452767d8c0f50532495000406d42a4bac366eebf5265c3be843a4e2169a8fd9f2d3b616ff3ab45ea221f39526d910ad5cb58c863691 1 0 \\x000000010000000000800003c0761b44fe967a4b7ad46c979f4c3374787f0f693d846efa38b8acbc1a1b9ff1a2ef4ade1520df07a904f048e3d4da7f7b070effa1041f61e96c46b5a50965bb0a9b6a8486478eda125b1a13b8c5c015aac105352d00c15a52c5a308cbe39b90f87e501b16b32bf894a3220802ec7950845c944b87cc4d81dce22e2378ad148f010001 \\xb9afc278ec16057d943c4fa046a3d0aecadf9809f1c91b70e1b5d0f478a1b5887e8717cd894684f1eab116ecec149ed57804020fe59c61a5c90864c7330e2b00 1661932407000000 1662537207000000 1725609207000000 1820217207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-407 \\xddc328ea9a7291fdb7e10d37c4f79c73523e91d0630cdb6b30d54219e1788f2b951d7f93194a548e8f1ee646583bb158d83910aa95e6ad79af6351a02c41d36d 1 0 \\x000000010000000000800003b037fc934fd95a17436f4e495291c1cf80fe8bd04cff43370876396b822137c9e428dd56fce60ceb0286c3544025f7273fb936f7e1f0d835dd77f4c187b8af4da1923db0de428397d3fdd1fb0388d18055c484c1241b5d82a3f9185741fffb93dbce23b8d5671d296279f75ab40a86bc6567ed629b1d0bd401fdf6c8cd9a28f9010001 \\x06d608cbc53e91a3e3c9f76e10c95da8e48ce57b9292bb37219d7b99b745d8cae74a2e0ddf77ecdf7c87c8419d499f4b669d8e7f3b270781dc62fb386f03d30e 1667372907000000 1667977707000000 1731049707000000 1825657707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-408 \\xdee7a5c873775fd4080265c6b378c040a45474a1ed5d4e7382815956e0c4bec4d5d168b81d1c8fb75037876c9c0b265ccbd08b7379e716b126ba465a23bcc985 1 0 \\x000000010000000000800003b72bfead6af31055e4faf30c1b7672f3133a17d8b1bb75c9b4e6e5782af4f3c0564a0e17a0a3f0cd69d19eb0ab8bc67c86f02fc23aab8b479b643c6f4e4b670427f1b27ae014db04660372ceac361617606afe0fe7437eb459101d591fc250a9918d1a169f4653062ae99f16f615921a0e9a686216e0d913bfcbf8a5a69cd333010001 \\x23c9ab07c55b026112644efc984bafae8b4427684f6122dd7d08efc153c4c3895e11bd15a60436ff97653d58bbbf44db034aaa2f10de3fa4c9cbfc909aa7f00b 1664954907000000 1665559707000000 1728631707000000 1823239707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-409 \\xe5f3915e7b0ff30245ecab05a5bd785009f379e599c57006b10f4aa50e5bd46cf3f61898befceb08810f9ea1c7410c76ff4fc4176de25db54083f139430476dc 1 0 \\x000000010000000000800003ca21fbb287b1757ab46ed3e4ec908ff9f7a33e9fdb038b0e36019d8a42dfd332a12473257f3cb568dcf8b64d902d514d56264b8567db8b9dc48bd1ae8406c561ac5ffe3375f0876b9e2435a7aa8f25e288035a2254176e28d96383b2bd7742a14dd70be4d42eb24a264ad640559776c5511d2c0f0c2ea41b685029b286f451cb010001 \\xe84198507e10c03b8ef22e12c23bf63590ff3eab4f10ace2f5a92b2e5ac30e073d02e31a01a1637762aa12128e702febaac5f464f1e929bc225c4801a9577e0f 1653469407000000 1654074207000000 1717146207000000 1811754207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-410 \\xe6776adb5e7d94f74f2b1d1c7cff6a7864340484a28b226a8e4e31a9078a001ec2daa7197937165b8865a8f4681bf7cce12e6f9f459ce8d2eeca3fc1097270ad 1 0 \\x000000010000000000800003c6bf1e60df8d6c06b3c901d21db54fcdffd1b2a06d118eead0cfd7520f908a6971cf926db45d2adc37e8cac4cda2aee8031e3b3da325e2a21b8d3398b1c3c4d3928b0dfdfde97dea1e2d796967ad04106796426b6c50d3c39a8ab5e077ec6a6a2605f6521267342520872b5f761095b9f22150c29ec791714f7fc29f2e901b83010001 \\x5aa893ed230b99d32e6d69373525cce448266db79600abc2fe0361d78c3e89515ccd4fa5358a2c11f40b45ac6f21ceba72d1dfa0baad71a9c5c18e5f4ebeba05 1645006407000000 1645611207000000 1708683207000000 1803291207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-411 \\xea33a43acaac9d46dc9ed659328724fca21f73eda75fd8d7682fa15418251648c8d71d534f0a50e6cf1e3aec8ee3f78321ce261f45ec60ea90014c5bdf96f973 1 0 \\x000000010000000000800003c874b9e3fb697a73d0595b23229b77945ec0b703a2eb06f56336fc6bb27884778c640856cf37b60aa1212466823902802c36bd8ab51bde7f7d4635b695339b5e3fd7355cd15db24cb6e6ba0fcbc43c146161ad47fa11c84002ef0ce212144d82838da2f9c7fa36c9c3fcd44acdc31dbdc745cdd1795c611f7ba0bd861f77c769010001 \\xedf7cab4da3f3f1934071bb846db999cbafe4ca7bd6130ce5b44a3888d3c1a4a4ae0eea0b30f7240f5fc61ca7479c4a200043c477206c8ce2d1b565fc498aa0c 1649842407000000 1650447207000000 1713519207000000 1808127207000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-412 \\xed23289b0e1f39f92121d8670dcb26a809a55d0bec14dc732de2a9bec755cbfec3ddde6e8951a5893e6328073586f59f96c2532dd77ea311c58c375f5a3d2e97 1 0 \\x000000010000000000800003b084aa1c3679a36d6b5eeac6fe88f2cd5b3345c52b4b6bd2b623df0af40186963719e1aaaf1dbcd46ce648514592a2f4d24c5c24f977985bf4c59225a2b719baac8bbd5dd4db736d425ae2f85e5aa6fc0fb2c056ba5f6019ff0b945630f1dc6791e5c536cb7e6bc252012eb36da7df455f477c2d149962e5011955029bc90fdd010001 \\x78d304075ff6632bb025987bccfb2c6aa8fdd0ecad485ef6862ec815157a2cf6d36a5fb2c9ecb456cf7cfc650432fb6b76d69e05b815da870bf44e58e7c72502 1657096407000000 1657701207000000 1720773207000000 1815381207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-413 \\xefdffbbca519758402a56e4e186b38e77ca4fceafc7935f029465184af1330229ac7e824bbde95f0ebf53757a4703af50911bd12d094970300701463d2974787 1 0 \\x000000010000000000800003d68416bc011ee9cc9724c2ec7252b5e2d192fce7428081dfec8410348d3d5780e0fd7c82e494724313f3198df76e3688583bf20ea016cd22241491c43a6c4b1e8c8ea857c2cc2776a7a4a46fcc1ddd65d8f1a41b9e2be958e4d9633e2a36bd4aae64edb98d0b9553756fda5e58257296ba5bdc0d71ea7c345eddc849822cce1b010001 \\xa327839831890e63356283bebb3b66647296011f956e0af3e4781d3b0303ff617dd9f392eab6f6ae1b2551fae8057419906e4986af6036131a1da9dd7b417d0f 1650446907000000 1651051707000000 1714123707000000 1808731707000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-414 \\xf1c365d5348d6655b3328d2d2a31f05413fd807ef61c513a0680a301ca90162fbe225bd0e561f1954cb3c273960ad2d888296c0ace3622680de9403def30fe18 1 0 \\x000000010000000000800003b707efc21bd92d7d38189e5530374ebefe15b899d7aeb4e41ecc393996cbaf55dccd9e25f6b7c96fc23795974e5277504dbc264215d48e54d161c259c033af18c83c0a33cbd011d33a8bee2314662f36f2905e028c056034b5abf3d199a597eeb49919e515fd9607a831cf43dc1f3ddd8b1add89051ac9a74b77d79d3eadba2b010001 \\xcfc5f4e6b593cb306f53218bfc3c6ee4d328fbc9f451da0c3bad8e2e8dc9f35939bb5ce1185c12208054f1bb1500afb083b07b77e17445f805d1b688e76f8f04 1640774907000000 1641379707000000 1704451707000000 1799059707000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-415 \\xf207b867ce9f54d9f19e8170ed444242406f2d8693678d924bc11c1b4a1b19235d8deab348e4c1740fae79af6e5d04127d7ed2419c5d3d3b267e3e9dafe36582 1 0 \\x000000010000000000800003b7d19309b9a5393cb6cf29ea3ca1a42ef8179d8a8907523fd7ec3379d5b533471eb652c76891780d7ab02499416d4fe4189261a11ec0bb1b75135904302a64245298ee871e617c0e83838fd668abaccf70a627b625a2c54c9b4c2c5394ef4e62a7a33d32a0a08feef25b9ba5d58ac14172dedfbc2607d6f06ec06eab4dd1c625010001 \\x85368c8d8cb02d22d1a4fa26e6f979c5f33e95e5b2e8678cc5d7ff21741a1cd56081c95f647fbcaacc0302c8d4f9908c3cfcab018806ff7d33c61a452eba4b07 1651051407000000 1651656207000000 1714728207000000 1809336207000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-416 \\xf5af984ef4b63bbad0524053c7d4427da00fe2de44c9c73a20a10d3211c540bbb74346823819cd0ca3d464d237a3922c44c0fb92071cdb6acc4d357f4ac49243 1 0 \\x000000010000000000800003aa5103953f746c41c89cd82ecbf44ddef0b0fa59d119153daa3acecbfd0b0e04fdee3274e01978a0173490a57981ca5da1af3260db397d41f5b84783567ecfaeeb2bdc05e02b29fa122b66d96adbf7e82fab5b5a459b40894b24447663bee17e239a5c6f31f6635203526c799b2b842467615efb67dfb0a6dbd3c60a5295ca85010001 \\x1766a88f90b81b7e00983cc8955114be30d70eb49e79546b722e5e9218914f1ddce748b5da58ec01233b1e3ee61b8d0e6258fea24ccd234147ac3429edf65300 1646215407000000 1646820207000000 1709892207000000 1804500207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-417 \\xf6f782d75ea7a470e3a7ffc79b78536abce6143954f364debb884140deb6f6bdc68ce6d8a41e21482da3ad79254d7528165040b5010009239006d172d2ed5d56 1 0 \\x000000010000000000800003b59048ffe432c0ed722638d3db58e83bf85ad46f2cd82d1ebcecd335bebc0385770f26420b32e6c851220829df7712b15f92c988ccb8f0834dc5625d0e503de6c397a181085cf2186fe74032d655f17c38316b46be3a05275fb4b40b0674dde5c44cca7e9473efe1bcd293c1b56c27234a275fb69b2f7f7ebadf5e60204f133d010001 \\x6b8177de8e63ea8aee346cee8a6acc45157fcba42619e6b74ca51c5b053a56221b80562c6a0920da1a2cb431bc8158cd88385cb60ccf52bcb5a65ede907f8f0b 1662536907000000 1663141707000000 1726213707000000 1820821707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-418 \\xf70f2890bb8dbe239357afa64b38f79d20c647096ac714e350e6517f2a2a09ee97c7a316027a362f6e5ce118b9e7b67f36ebf5b5ab9a3fac2c625cbd28e53692 1 0 \\x000000010000000000800003a9877e18d9a3950dd58619475efe0b2de838842f612503e8f5dd6e5a23279c6515611b8dacd2dd61e262079bcffb883c6e36054b36cfa0e8794c6102267e501c357211859112edd3f80837354efc2de10b74585b62c59cd035c5f6a096e90b68ba814e475d0c2d79ee8015cc9c23b3148e6d8914e4b100134f02c7923f28dc03010001 \\xc32439a01911b822d3d3c2d08993520bfb5a4c4774e1be252aa012252a5a5717749bd455cb03add23863d0da4dff98010acc462810225572e490fffafb177e07 1666163907000000 1666768707000000 1729840707000000 1824448707000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-419 \\xf90f2a38dffd6fab044e7b827b1da1792c772c665bda398a31ed823af5fc00779770c09ed7be2abc5556aded53c6fb032928abd268fe917724e729f33075d4b6 1 0 \\x000000010000000000800003c3f44c0b78ec94fce547bb062fcba2d13aa5e5ab6c298399a43f47d1da8166d26370711f6e53d1911616c873c724ff4cc3b83ffb62275c07335c8d79203f4d07baceb0af4b1e95691d69b5a2344c268e0f295680013d575c8087a5eac71a4390777c76a13bac16deb3e4563e34c63fb13b8a8ee6ad093cba39c9c51846944aad010001 \\x77521419902bf79cbbc6a79c06937e362c581c4baa3536a55c5ee547b661a99349de7af801c4c1aa92876a513a14727b6e5c50d00d19cbe441a38b867b686909 1646215407000000 1646820207000000 1709892207000000 1804500207000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-420 \\xfa134c4c491cccf953a7768fe942db20234765ab44df157781e6f60e05076c0af9d89c3c5cf16bcff18751f04d1247fcd4d1afd3a9f5d62bbd4671b382a3259e 1 0 \\x000000010000000000800003b48e504dc0e47b3bba11298708741d8b41d2a94da6b0fa8e5f3057ec0f05f164eeaef8293625d2b95975c7c094d86ca329a5710b5db576af6eedb333f26fb80fdbb163c8c99e846efb0cd88e55a33d276e442a27111364adb0bfb7a5eb9557e4ff3166b68e4ff19f71fe85a04c51aff2dc5fb2d744b36a6fca79e7f980d38771010001 \\x2508735219f6e6f13976a5683a940a004a1861e2eb03e66141e3c25413ab5eb9732cc76aba197212e40cd2d34ebd6a4efd648e7a5787aae7e295a76fd02cd905 1651655907000000 1652260707000000 1715332707000000 1809940707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-421 \\xfa4b3180c4565700be2cfeaa68eb5eeff1ec05fe70a3ccdc1708a984cc6bd74dde8ed63858e8f185c1c7f177f8c5d5f84a5b8f8b74c09f84ae475ec706582060 1 0 \\x000000010000000000800003a41ee50ff4ac56eb446ccce9e0f2188baa7e3234576a8a0b6854582e925fc52360f56af6a6115040dc27dbe10633302fc1c63601fe22a755f3bdad4bd08281eff0e096d39098ef50f7724858c69050342ea9dfbf73b97c8f944e01c29bbcaf9d2a493ea158d643c9247a7a9125f028bc5117fc8d3e27bf7a8a47e9923ea84e4d010001 \\xece054072f0b7857bcdc8ea0ae507d9b5dec7bef3676cc548630525b8d5d423469dbffce0b38513c9846f763b8d381acd8a69002198740374f6d755f4e1ece02 1652864907000000 1653469707000000 1716541707000000 1811149707000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-422 \\xfb8361b479b6a5883e276dd8446c325dc780cd3b458f1611f5e984c997cb86766f29962d31a15ef7e304f3d499cfc946dd736f89ef8d37ab3daedff5e486276f 1 0 \\x000000010000000000800003ef04cfab9bd14575bf540a3fb7ebbdefe7373af8354312175e2fe589852cd612520d99c7efe0216ced953f2b7cad6dbcae98c400306d66183c285b57dd02170c89bf0798f87d6e4d47990ff6c75ab8151d0f313aa7eb5d5931b2a335ca02f47e64296f96b91ceb8cd6224dc33079b20feb1dffbe0d951b11b0970d9f6ffc259b010001 \\xcd3065fb59fce9f8af2013c623c721472e1a2e30eb5d82d2fd564769c8ce54dcfdf45713a811eeb70dd7edbcc61b60f145e365254cb90bef8abcd07abb52bf01 1666768407000000 1667373207000000 1730445207000000 1825053207000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-423 \\xfccbee203a96da3fd875f2ebe46a1c4389590556a4e69ccac39f63835fd371459d3374bf2663cf863d6b507d5da43caf0cf3a69f0db6b8ee2f673e9b84480828 1 0 \\x000000010000000000800003adac992cf9e68952f02993be8f9c0cfc3fba22b582033503a5a8a8c4bc80f2b36af840882ab3ebfb650925e4d53fbd8d7a4651e0c2a2616c7313f376ecd26e91c94771b6cc9c937c2e05cf095946a286ab0ab17cf8885f7ee84abd73d46552ed8fcf5cfc71d68421e06a50e649e6bd6d355cceec28c29099ee34ea9509ee5779010001 \\x33b499ec6459710ae761188db64d17879cf62410f52dc38b63bd619c341a82a2571c7c8df278fde9d48d015e1e4f03427c903e336e448b1e15feddb04bc9ac03 1642588407000000 1643193207000000 1706265207000000 1800873207000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-424 \\xfe276e4add494fa4c9a490cd1b8d1fc88641547dc52ff9c9c66d17cef07b836645c262f48df3f8d33e146dc9475f13719c23abfba527f103f1ffe5ff5450e333 1 0 \\x000000010000000000800003acaf8a1d540e4d0710dced5347b93e5c50e5e4b2d78fb2317f86a9cdf63b64f489c14670db3ac4661a15606c50d76e4599415bbbaa51770e91a07956a857ec71fee2dc3cefcb02b2695cf8d91a3e4dd508fa1324a4b12b562d8617f418932fb9f450760397ee9d9d4222757bc93cbf20a2836f234a625f16a9c823f428673e5b010001 \\xa747db57c0d4eec99391faa364e23bce7d005635ae51905e54dab14f5ddf185eacfacb706aa42b4ff482d0bd93f326a60d98b498447682e96372aa27cab24304 1664954907000000 1665559707000000 1728631707000000 1823239707000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-\.
-
-
---
--- Data for Name: deposit_confirmations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.deposit_confirmations (master_pub, serial_id, h_contract_terms, h_extensions, h_wire, exchange_timestamp, refund_deadline, wire_deadline, amount_without_fee_val, amount_without_fee_frac, coin_pub, merchant_pub, exchange_sig, exchange_pub, master_sig) FROM stdin;
-\\xc224b524084bc7ec63376db8b4899f2ec3b0482bb6f8890560b236cdc4e167bc 1 \\x8ff396e353930af5c419a356bac9e48feda8764c688f65fd13107b10b270f2dd16aa44a6876cfb02542834ffd6d98da9066c23ad69c1645f8388d441cffc4674 \\x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \\xa42c95cbe4589410d07454e975c526f62721b8d9b7e6184c027a8b9dfd522578cda79ae6d90b306b07ec4d2c50c4604b50bfcdfba507bfe2f0da9d45d84a788c 1638356927000000 1638357825000000 1638357825000000 3 98000000 \\x864b23a89d412fde2af83cf9bb078a655b52aa34299a13c7ef391ade35e1c89f \\x36e1584ea836c7f6fb49a20f07f13b9d25fc069dada9669c8572ed096c63799c \\x2dff75327b84f774b7819d4654c9c64c96e5c6f4784c7fdcc14495156e06c7d0409e5594a7b3484b3087e23becf6be92508d6d59ebadbddd35173b947c8be70f \\x70e9063063f4dc2e3532a458ab590fa6c322362e45c3d19736b28f770d79af0c \\x00000000000000002f20da7bfc7f00004f20da7bfc7f00001020da7bfc7f00004f20da7bfc7f00000000000000000000000000000000000000cf64f701113b71
-\\xc224b524084bc7ec63376db8b4899f2ec3b0482bb6f8890560b236cdc4e167bc 2 \\xa915f2d530da9b250dee47e6375c4a95dd9f722600da4fe78b1a7c796271f9f213c2e1a8707084bb732b3e32f0e59ac16b40e5600d861af32370812c10fbb03e \\x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \\xa42c95cbe4589410d07454e975c526f62721b8d9b7e6184c027a8b9dfd522578cda79ae6d90b306b07ec4d2c50c4604b50bfcdfba507bfe2f0da9d45d84a788c 1638356935000000 1638357833000000 1638357833000000 6 99000000 \\x1e0c2ac6166a810df10d007612059e812beb4865acd20d14b8fb337dc4eaef00 \\x36e1584ea836c7f6fb49a20f07f13b9d25fc069dada9669c8572ed096c63799c \\xaa022ffb061f9e3c3b6f4272422b450c5872238b036b91c2b1615dc28da996babe3c263913f59a7a15ce4b102309781f4dad5a3bba86364a6742f0e1a2b7210f \\x70e9063063f4dc2e3532a458ab590fa6c322362e45c3d19736b28f770d79af0c \\x00000000000000002f20da7bfc7f00004f20da7bfc7f00001020da7bfc7f00004f20da7bfc7f00000000000000000000000000000000000000cf64f701113b71
-\\xc224b524084bc7ec63376db8b4899f2ec3b0482bb6f8890560b236cdc4e167bc 3 \\x9b46047240a728295adef55198dec4167e56abaeb0d6bd2652bf269028d617062691ade7c485292e82ed684208916cd2d657dfc6ac1ac4a5bd997d4daa9a1530 \\x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \\xa42c95cbe4589410d07454e975c526f62721b8d9b7e6184c027a8b9dfd522578cda79ae6d90b306b07ec4d2c50c4604b50bfcdfba507bfe2f0da9d45d84a788c 1638356943000000 1638357840000000 1638357840000000 2 99000000 \\x4cd9aa99d5383b0d249f0c82424ea26490fa945aa7bf35a6552402c9fc635c73 \\x36e1584ea836c7f6fb49a20f07f13b9d25fc069dada9669c8572ed096c63799c \\x2adb5f3069c939c45c0c9560543f63259540c9d8e694cf7424a0c2dd1fa86cc399c196bbc86fb76ee5dc4fede7ab899cc861a6ada9fc46a600ba3cf76f542900 \\x70e9063063f4dc2e3532a458ab590fa6c322362e45c3d19736b28f770d79af0c \\x00000000000000002f20da7bfc7f00004f20da7bfc7f00001020da7bfc7f00004f20da7bfc7f00000000000000000000000000000000000000cf64f701113b71
-\.
-
-
---
--- Data for Name: deposits; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.deposits (deposit_serial_id, shard, known_coin_id, amount_with_fee_val, amount_with_fee_frac, wallet_timestamp, exchange_timestamp, refund_deadline, wire_deadline, merchant_pub, h_contract_terms, coin_sig, wire_salt, wire_target_serial_id, tiny, done, extension_blocked, extension_details_serial_id) FROM stdin;
-1 676795002 1 4 0 1638356925000000 1638356927000000 1638357825000000 1638357825000000 \\x36e1584ea836c7f6fb49a20f07f13b9d25fc069dada9669c8572ed096c63799c \\x8ff396e353930af5c419a356bac9e48feda8764c688f65fd13107b10b270f2dd16aa44a6876cfb02542834ffd6d98da9066c23ad69c1645f8388d441cffc4674 \\x782d66d05eeb8156ace439bcea6943c212fe06aeb1a528f71b62f299a4f9afe427c225675f19087c43a82a7dcb3b3feaafbe5111bef858eb5bd7fb654b1bf70a \\x0849796f95f39370b7eaa8c0d2fe2327 2 f f f \N
-2 676795002 2 7 0 1638356933000000 1638356935000000 1638357833000000 1638357833000000 \\x36e1584ea836c7f6fb49a20f07f13b9d25fc069dada9669c8572ed096c63799c \\xa915f2d530da9b250dee47e6375c4a95dd9f722600da4fe78b1a7c796271f9f213c2e1a8707084bb732b3e32f0e59ac16b40e5600d861af32370812c10fbb03e \\xcf9283a6e88cdf5480f5b22a6c22720371f1fe575f6ff1f1477501064dc699b3b34ee0d55ffbca4f09ab51df39424897619fb4b9659de0fa0cedf5834658fb0a \\x0849796f95f39370b7eaa8c0d2fe2327 2 f f f \N
-3 676795002 3 3 0 1638356940000000 1638356943000000 1638357840000000 1638357840000000 \\x36e1584ea836c7f6fb49a20f07f13b9d25fc069dada9669c8572ed096c63799c \\x9b46047240a728295adef55198dec4167e56abaeb0d6bd2652bf269028d617062691ade7c485292e82ed684208916cd2d657dfc6ac1ac4a5bd997d4daa9a1530 \\x393358c58a375567de85e2f496bd60553ad297c93ad96a53dca91cc8c0a7982db47dc6e41cd2de364799387e304a2a4ff3b5f6e81d2a387d55b45b90ec0c350c \\x0849796f95f39370b7eaa8c0d2fe2327 2 f f f \N
-\.
-
-
---
--- 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 2021-12-01 12:08:28.114454+01
-2 auth 0001_initial 2021-12-01 12:08:28.219049+01
-3 app 0001_initial 2021-12-01 12:08:28.289405+01
-4 app 0002_auto_20211103_1517 2021-12-01 12:08:28.291863+01
-5 app 0003_auto_20211103_1518 2021-12-01 12:08:28.294599+01
-6 app 0004_auto_20211103_1519 2021-12-01 12:08:28.29681+01
-7 app 0005_auto_20211103_1519 2021-12-01 12:08:28.29883+01
-8 app 0006_auto_20211103_1520 2021-12-01 12:08:28.301082+01
-9 app 0007_auto_20211103_1520 2021-12-01 12:08:28.304051+01
-10 contenttypes 0002_remove_content_type_name 2021-12-01 12:08:28.315131+01
-11 auth 0002_alter_permission_name_max_length 2021-12-01 12:08:28.322694+01
-12 auth 0003_alter_user_email_max_length 2021-12-01 12:08:28.32838+01
-13 auth 0004_alter_user_username_opts 2021-12-01 12:08:28.333899+01
-14 auth 0005_alter_user_last_login_null 2021-12-01 12:08:28.33956+01
-15 auth 0006_require_contenttypes_0002 2021-12-01 12:08:28.34173+01
-16 auth 0007_alter_validators_add_error_messages 2021-12-01 12:08:28.347167+01
-17 auth 0008_alter_user_username_max_length 2021-12-01 12:08:28.35759+01
-18 auth 0009_alter_user_last_name_max_length 2021-12-01 12:08:28.363555+01
-19 auth 0010_alter_group_name_max_length 2021-12-01 12:08:28.370757+01
-20 auth 0011_update_proxy_permissions 2021-12-01 12:08:28.378008+01
-21 auth 0012_alter_user_first_name_max_length 2021-12-01 12:08:28.383754+01
-22 sessions 0001_initial 2021-12-01 12:08:28.402962+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_sign_keys; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.exchange_sign_keys (esk_serial, exchange_pub, master_sig, valid_from, expire_sign, expire_legal) FROM stdin;
-1 \\x8256a7d9b2d88222b0a712440b18497a44fe0415ad273f0af8f8cbda1ddbd9c1 \\x13bcca62575c0fb2e41ca366031ae84da7fee7cbc266eeb24efc64e9d47a6a35376c4af44f5e4916ca27ff356fa3b8918a9e6ba10c54c8a66fc85cf8bb272000 1652871507000000 1660129107000000 1662548307000000
-2 \\x6a7c9f52129ebc445f3ebea3eb3ca9fce6c1b89c7d755b05ae29db3909b2ee11 \\x9f5394c955b7aa653d1e8438447136f2dd6e819f182c6b8a7191c62baf4e2d0859796a55c4d8e91d4a574b02e652ff7ffd43eb8f5e053752cafc621d5b259506 1645614207000000 1652871807000000 1655291007000000
-3 \\x70e9063063f4dc2e3532a458ab590fa6c322362e45c3d19736b28f770d79af0c \\xa96df026c9d79a0556d6ed540e8cd4465ff397bef464c3d644f401b3f3c27f4ae4c04a21463d16f75d1275404447c02778884e4370086400c3eca5e4f6d19107 1638356907000000 1645614507000000 1648033707000000
-4 \\x7cca75492be957360b96eb8bc06b45fbb395845edd482dab5e72aa84c64615a4 \\x667ac2faf237c1ec5c18013184af23aa59593f6395b949c32b8b4ae17c984c795f59afe87e4b80b9b5605f45d19deb171b0ce1630868ec808937ba277edfb000 1660128807000000 1667386407000000 1669805607000000
-5 \\xff2a8f708a9c59bea4f2b4b9458682804452ed63005234c01a3cf995299e8e86 \\x65cbaa87d6229e799b3e49a5058899745bc3a224dc0a496f6c3fa5d3b6e8bb62ca7348315f29d6d14f9b77236cb26dc6017f6a8654e7e8a500c7087115857206 1667386107000000 1674643707000000 1677062907000000
-\.
-
-
---
--- Data for Name: extension_details; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.extension_details (extension_details_serial_id, extension_options) FROM stdin;
-\.
-
-
---
--- Data for Name: known_coins; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.known_coins (known_coin_id, coin_pub, age_hash, denominations_serial, denom_sig) FROM stdin;
-1 \\x864b23a89d412fde2af83cf9bb078a655b52aa34299a13c7ef391ade35e1c89f \N 224 \\x0000000100000000b412ac0632df5a1940cc43e3e56f6c2ba2de98ab9404b0b10f87ac51b74aeeb625624138b07cb86fc0d2e6d4b827aba0909e2d096e9e46d18d57508fb4741a8f2e66b6524c80c21751e77376b6975aa9a90b8d8cfcfb0f72aca70aa77bd43fb633cc415c80555dd1a4a9fbbedd8affa6ae12dad7c0bb326719b67467315073a8
-2 \\x1e0c2ac6166a810df10d007612059e812beb4865acd20d14b8fb337dc4eaef00 \N 155 \\x0000000100000000aa0ea95a18c4f52cceb7fdc0dc2233d0a0ac6cf3d92b1356037c865c49207ef8405769926999095a25a1d211803f20995dc3a1c18c29341f6cf91de63942068278007dd00b5d46101402810bb7ff23efce865301f3d1f01866c2d62c3e2b25c634a4df60ccdf2b7fe947d5c8959704eb58e5f2cefce0b1418b13af600f6767d2
-3 \\x4cd9aa99d5383b0d249f0c82424ea26490fa945aa7bf35a6552402c9fc635c73 \N 158 \\x0000000100000000af79a3fc715f12fadf87207e01714aeec8e97471d49a5f98189551ba9b677c208829175e0c687e69d4a1a6e10b498f588dccf7c771b463e26d7164a48da01b4f3e76ba8b36ffbd84fff43dd5c81e59d5883def770c55eb4c7a3bf9021766f6e67358be70f7839aada93784e537baded5d1309b0de54db38e8b428a9584bae1a9
-\.
-
-
---
--- Data for Name: merchant_accounts; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_accounts (account_serial, merchant_serial, h_wire, salt, payto_uri, active) FROM stdin;
-1 1 \\xa42c95cbe4589410d07454e975c526f62721b8d9b7e6184c027a8b9dfd522578cda79ae6d90b306b07ec4d2c50c4604b50bfcdfba507bfe2f0da9d45d84a788c \\x0849796f95f39370b7eaa8c0d2fe2327 payto://x-taler-bank/localhost/43 t
-\.
-
-
---
--- Data for Name: merchant_contract_terms; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_contract_terms (order_serial, merchant_serial, order_id, contract_terms, h_contract_terms, creation_time, pay_deadline, refund_deadline, paid, wired, fulfillment_url, session_id, claim_token) FROM stdin;
-1 1 2021.335-00P4MHRW2TJJ6 \\x7b22616d6f756e74223a22544553544b55444f533a34222c2273756d6d617279223a2268656c6c6f20776f726c64222c2266756c66696c6c6d656e745f75726c223a2274616c65723a2f2f66756c66696c6c6d656e742d737563636573732f746878222c22726566756e645f646561646c696e65223a7b22745f6d73223a313633383335373832353030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313633383335373832353030307d2c2270726f6475637473223a5b5d2c22685f77697265223a224d475039424a5a3442324131314d334d414b4d514248393659524b4a33453653505a4b31474b303246413553565a414a344e5743563957545756434750433342305a50345442324752484734504d355a535158544131585a574252444e37413556313537483330222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226f726465725f6964223a22323032312e3333352d303050344d48525732544a4a36222c2274696d657374616d70223a7b22745f6d73223a313633383335363932353030307d2c227061795f646561646c696e65223a7b22745f6d73223a313633383336303532353030307d2c226d61785f776972655f666565223a22544553544b55444f533a31222c226d61785f666565223a22544553544b55444f533a31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f222c226d65726368616e74223a7b226e616d65223a2264656661756c74222c2261646472657373223a7b7d2c226a7572697364696374696f6e223a7b7d7d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a2252384a42413930383946335952525351445057423932435a35563156304a3142505657384a314230503856435648373143595930227d5d2c2261756469746f7273223a5b5d2c226d65726368616e745f707562223a223656474e474b4e383656335a445954394d383747465739564b4d4a5a52314d584e504d5044373435454250474a56333346364530222c226e6f6e6365223a223739424638314b5a5a32365144304757415a4b4a344b543852335051484632364b42594b4b463748303257504147355345325147227d \\x8ff396e353930af5c419a356bac9e48feda8764c688f65fd13107b10b270f2dd16aa44a6876cfb02542834ffd6d98da9066c23ad69c1645f8388d441cffc4674 1638356925000000 1638360525000000 1638357825000000 t f taler://fulfillment-success/thx \\x29095e9e1c56b99221b0435a57b0ff07
-2 1 2021.335-G18ZR5P051Z0P \\x7b22616d6f756e74223a22544553544b55444f533a37222c2273756d6d617279223a226f7264657220746861742077696c6c20626520726566756e646564222c2266756c66696c6c6d656e745f75726c223a2274616c65723a2f2f66756c66696c6c6d656e742d737563636573732f746878222c22726566756e645f646561646c696e65223a7b22745f6d73223a313633383335373833333030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313633383335373833333030307d2c2270726f6475637473223a5b5d2c22685f77697265223a224d475039424a5a3442324131314d334d414b4d514248393659524b4a33453653505a4b31474b303246413553565a414a344e5743563957545756434750433342305a50345442324752484734504d355a535158544131585a574252444e37413556313537483330222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226f726465725f6964223a22323032312e3333352d4731385a5235503035315a3050222c2274696d657374616d70223a7b22745f6d73223a313633383335363933333030307d2c227061795f646561646c696e65223a7b22745f6d73223a313633383336303533333030307d2c226d61785f776972655f666565223a22544553544b55444f533a31222c226d61785f666565223a22544553544b55444f533a31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f222c226d65726368616e74223a7b226e616d65223a2264656661756c74222c2261646472657373223a7b7d2c226a7572697364696374696f6e223a7b7d7d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a2252384a42413930383946335952525351445057423932435a35563156304a3142505657384a314230503856435648373143595930227d5d2c2261756469746f7273223a5b5d2c226d65726368616e745f707562223a223656474e474b4e383656335a445954394d383747465739564b4d4a5a52314d584e504d5044373435454250474a56333346364530222c226e6f6e6365223a2246564d4a3336543541395136484b5a474a385752544a39484b41345947393437483554545a45504d5a30375a413932354b4d5647227d \\xa915f2d530da9b250dee47e6375c4a95dd9f722600da4fe78b1a7c796271f9f213c2e1a8707084bb732b3e32f0e59ac16b40e5600d861af32370812c10fbb03e 1638356933000000 1638360533000000 1638357833000000 t f taler://fulfillment-success/thx \\xe6700b8912c9d69c229b10c290a774a4
-3 1 2021.335-03A8KA76X5258 \\x7b22616d6f756e74223a22544553544b55444f533a33222c2273756d6d617279223a227061796d656e7420616674657220726566756e64222c2266756c66696c6c6d656e745f75726c223a2274616c65723a2f2f66756c66696c6c6d656e742d737563636573732f746878222c22726566756e645f646561646c696e65223a7b22745f6d73223a313633383335373834303030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313633383335373834303030307d2c2270726f6475637473223a5b5d2c22685f77697265223a224d475039424a5a3442324131314d334d414b4d514248393659524b4a33453653505a4b31474b303246413553565a414a344e5743563957545756434750433342305a50345442324752484734504d355a535158544131585a574252444e37413556313537483330222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226f726465725f6964223a22323032312e3333352d303341384b4137365835323538222c2274696d657374616d70223a7b22745f6d73223a313633383335363934303030307d2c227061795f646561646c696e65223a7b22745f6d73223a313633383336303534303030307d2c226d61785f776972655f666565223a22544553544b55444f533a31222c226d61785f666565223a22544553544b55444f533a31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f222c226d65726368616e74223a7b226e616d65223a2264656661756c74222c2261646472657373223a7b7d2c226a7572697364696374696f6e223a7b7d7d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a2252384a42413930383946335952525351445057423932435a35563156304a3142505657384a314230503856435648373143595930227d5d2c2261756469746f7273223a5b5d2c226d65726368616e745f707562223a223656474e474b4e383656335a445954394d383747465739564b4d4a5a52314d584e504d5044373435454250474a56333346364530222c226e6f6e6365223a2230413544384e3554305358324b4257344b3346444a59485752534d47385647354a57475353423453364154354139305948313930227d \\x9b46047240a728295adef55198dec4167e56abaeb0d6bd2652bf269028d617062691ade7c485292e82ed684208916cd2d657dfc6ac1ac4a5bd997d4daa9a1530 1638356940000000 1638360540000000 1638357840000000 t f taler://fulfillment-success/thx \\xd75efc2f0d8ae3092342d1545462dabd
-\.
-
-
---
--- Data for Name: merchant_deposit_to_transfer; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_deposit_to_transfer (deposit_serial, coin_contribution_value_val, coin_contribution_value_frac, credit_serial, execution_time, signkey_serial, exchange_sig) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_deposits; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_deposits (deposit_serial, order_serial, deposit_timestamp, 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_serial, exchange_sig, account_serial) FROM stdin;
-1 1 1638356927000000 \\x864b23a89d412fde2af83cf9bb078a655b52aa34299a13c7ef391ade35e1c89f http://localhost:8081/ 4 0 0 2000000 0 4000000 0 1000000 3 \\x2dff75327b84f774b7819d4654c9c64c96e5c6f4784c7fdcc14495156e06c7d0409e5594a7b3484b3087e23becf6be92508d6d59ebadbddd35173b947c8be70f 1
-2 2 1638356935000000 \\x1e0c2ac6166a810df10d007612059e812beb4865acd20d14b8fb337dc4eaef00 http://localhost:8081/ 7 0 0 1000000 0 1000000 0 1000000 3 \\xaa022ffb061f9e3c3b6f4272422b450c5872238b036b91c2b1615dc28da996babe3c263913f59a7a15ce4b102309781f4dad5a3bba86364a6742f0e1a2b7210f 1
-3 3 1638356943000000 \\x4cd9aa99d5383b0d249f0c82424ea26490fa945aa7bf35a6552402c9fc635c73 http://localhost:8081/ 3 0 0 1000000 0 1000000 0 1000000 3 \\x2adb5f3069c939c45c0c9560543f63259540c9d8e694cf7424a0c2dd1fa86cc399c196bbc86fb76ee5dc4fede7ab899cc861a6ada9fc46a600ba3cf76f542900 1
-\.
-
-
---
--- Data for Name: merchant_exchange_signing_keys; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_exchange_signing_keys (signkey_serial, master_pub, exchange_pub, start_date, expire_date, end_date, master_sig) FROM stdin;
-1 \\xc224b524084bc7ec63376db8b4899f2ec3b0482bb6f8890560b236cdc4e167bc \\x8256a7d9b2d88222b0a712440b18497a44fe0415ad273f0af8f8cbda1ddbd9c1 1652871507000000 1660129107000000 1662548307000000 \\x13bcca62575c0fb2e41ca366031ae84da7fee7cbc266eeb24efc64e9d47a6a35376c4af44f5e4916ca27ff356fa3b8918a9e6ba10c54c8a66fc85cf8bb272000
-2 \\xc224b524084bc7ec63376db8b4899f2ec3b0482bb6f8890560b236cdc4e167bc \\x6a7c9f52129ebc445f3ebea3eb3ca9fce6c1b89c7d755b05ae29db3909b2ee11 1645614207000000 1652871807000000 1655291007000000 \\x9f5394c955b7aa653d1e8438447136f2dd6e819f182c6b8a7191c62baf4e2d0859796a55c4d8e91d4a574b02e652ff7ffd43eb8f5e053752cafc621d5b259506
-3 \\xc224b524084bc7ec63376db8b4899f2ec3b0482bb6f8890560b236cdc4e167bc \\x70e9063063f4dc2e3532a458ab590fa6c322362e45c3d19736b28f770d79af0c 1638356907000000 1645614507000000 1648033707000000 \\xa96df026c9d79a0556d6ed540e8cd4465ff397bef464c3d644f401b3f3c27f4ae4c04a21463d16f75d1275404447c02778884e4370086400c3eca5e4f6d19107
-4 \\xc224b524084bc7ec63376db8b4899f2ec3b0482bb6f8890560b236cdc4e167bc \\x7cca75492be957360b96eb8bc06b45fbb395845edd482dab5e72aa84c64615a4 1660128807000000 1667386407000000 1669805607000000 \\x667ac2faf237c1ec5c18013184af23aa59593f6395b949c32b8b4ae17c984c795f59afe87e4b80b9b5605f45d19deb171b0ce1630868ec808937ba277edfb000
-5 \\xc224b524084bc7ec63376db8b4899f2ec3b0482bb6f8890560b236cdc4e167bc \\xff2a8f708a9c59bea4f2b4b9458682804452ed63005234c01a3cf995299e8e86 1667386107000000 1674643707000000 1677062907000000 \\x65cbaa87d6229e799b3e49a5058899745bc3a224dc0a496f6c3fa5d3b6e8bb62ca7348315f29d6d14f9b77236cb26dc6017f6a8654e7e8a500c7087115857206
-\.
-
-
---
--- Data for Name: merchant_exchange_wire_fees; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_exchange_wire_fees (wirefee_serial, master_pub, h_wire_method, start_date, end_date, wire_fee_val, wire_fee_frac, closing_fee_val, closing_fee_frac, master_sig) FROM stdin;
-1 \\xc224b524084bc7ec63376db8b4899f2ec3b0482bb6f8890560b236cdc4e167bc \\xf9099467bd884e86871559a62a7f23b6e876bf084a30371891b5129ce4440d3cbe27afe387d39b2ce8d9625abd388517c81bfc8da9f2e0f8c9471bff65a802b2 1609459200000000 1640995200000000 0 1000000 0 1000000 \\x0d82feaa94d63ddd13346840135e4a74893bb825ac9838a99a3861024eef918e5af74401e3cddc7c8202d6f84be76f5256454d4af258d1bd9978d3a9499f1501
-\.
-
-
---
--- Data for Name: merchant_instances; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_instances (merchant_serial, merchant_pub, merchant_id, merchant_name, address, jurisdiction, default_max_deposit_fee_val, default_max_deposit_fee_frac, default_max_wire_fee_val, default_max_wire_fee_frac, default_wire_fee_amortization, default_wire_transfer_delay, default_pay_delay, auth_hash, auth_salt) FROM stdin;
-1 \\x36e1584ea836c7f6fb49a20f07f13b9d25fc069dada9669c8572ed096c63799c default default \\x7b7d \\x7b7d 1 0 1 0 1 3600000000 3600000000 \\x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \\x0000000000000000000000000000000000000000000000000000000000000000
-\.
-
-
---
--- Data for Name: merchant_inventory; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_inventory (product_serial, merchant_serial, product_id, description, description_i18n, unit, image, taxes, price_val, price_frac, total_stock, total_sold, total_lost, address, next_restock) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_inventory_locks; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_inventory_locks (product_serial, lock_uuid, total_locked, expiration) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_keys; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_keys (merchant_priv, merchant_serial) FROM stdin;
-\\xb723a3a86b8a93bc6c3d1495b7160064b1b3a09e3b618cc17e9f3dc24904b976 1
-\.
-
-
---
--- Data for Name: merchant_kyc; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_kyc (kyc_serial_id, kyc_timestamp, kyc_ok, exchange_sig, exchange_pub, exchange_kyc_serial, account_serial, exchange_url) FROM stdin;
-1 1638356927000000 f \N \N 2 1 http://localhost:8081/
-\.
-
-
---
--- Data for Name: merchant_order_locks; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_order_locks (product_serial, total_locked, order_serial) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_orders; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_orders (order_serial, merchant_serial, order_id, claim_token, h_post_data, pay_deadline, creation_time, contract_terms) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_refund_proofs; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_refund_proofs (refund_serial, exchange_sig, signkey_serial) FROM stdin;
-1 \\x4fa01ccab8d3a0cfbc8d27cb988dac3a272072feba6fed11c6168231fe420f5f9a487cc49ee2fda2251eb460f42c0602a729327c235e4e70e61f6ba384a0fb0d 3
-\.
-
-
---
--- Data for Name: merchant_refunds; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_refunds (refund_serial, order_serial, rtransaction_id, refund_timestamp, coin_pub, reason, refund_amount_val, refund_amount_frac) FROM stdin;
-1 2 1 1638356936000000 \\x1e0c2ac6166a810df10d007612059e812beb4865acd20d14b8fb337dc4eaef00 test refund 6 0
-\.
-
-
---
--- Data for Name: merchant_tip_pickup_signatures; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tip_pickup_signatures (pickup_serial, coin_offset, blind_sig) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tip_pickups; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tip_pickups (pickup_serial, tip_serial, pickup_id, amount_val, amount_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tip_reserve_keys; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tip_reserve_keys (reserve_serial, reserve_priv, exchange_url, payto_uri) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tip_reserves; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tip_reserves (reserve_serial, reserve_pub, merchant_serial, creation_time, expiration, merchant_initial_balance_val, merchant_initial_balance_frac, exchange_initial_balance_val, exchange_initial_balance_frac, tips_committed_val, tips_committed_frac, tips_picked_up_val, tips_picked_up_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tips; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tips (tip_serial, reserve_serial, tip_id, justification, next_url, expiration, amount_val, amount_frac, picked_up_val, picked_up_frac, was_picked_up) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_transfer_signatures; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_transfer_signatures (credit_serial, signkey_serial, wire_fee_val, wire_fee_frac, execution_time, exchange_sig, credit_amount_val, credit_amount_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_transfer_to_coin; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_transfer_to_coin (deposit_serial, credit_serial, offset_in_exchange_list, exchange_deposit_value_val, exchange_deposit_value_frac, exchange_deposit_fee_val, exchange_deposit_fee_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_transfers; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_transfers (credit_serial, exchange_url, wtid, credit_amount_val, credit_amount_frac, account_serial, verified, confirmed) FROM stdin;
-\.
-
-
---
--- Data for Name: prewire; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.prewire (prewire_uuid, type, finished, failed, buf) FROM stdin;
-\.
-
-
---
--- Data for Name: recoup; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.recoup (recoup_uuid, known_coin_id, coin_sig, coin_blind, amount_val, amount_frac, "timestamp", reserve_out_serial_id) FROM stdin;
-\.
-
-
---
--- Data for Name: recoup_refresh; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.recoup_refresh (recoup_refresh_uuid, known_coin_id, coin_sig, coin_blind, amount_val, amount_frac, "timestamp", rrc_serial) FROM stdin;
-\.
-
-
---
--- Data for Name: refresh_commitments; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refresh_commitments (melt_serial_id, rc, old_known_coin_id, old_coin_sig, amount_with_fee_val, amount_with_fee_frac, noreveal_index) FROM stdin;
-1 \\x9a7473637436a219f834f9d5c6fea8805576f4cc9f4d2c0ba7ce4655b3c134d0491d1e4ce0ed732c3ba4e0b708165c08dd561f9c84ca44dc75e4fdd7645b2402 1 \\x6580ccaa66f5bbc327426e4a6e13c8b9f59bd9ee94b49a33ee4eb0b7a0b3f1238b01bb9fb924b83ad80f15afda29cd31b94cba06fe17f902bbe0713f62cf5001 4 0 2
-2 \\x471ff0e881d4b16132adbbfdd817ccd9e0348a328bd2b94ce43bc9d0439f6c23211d1dfce1553e751cd3375261dfca9ec45abb1c46b09103abae1785db2d501c 2 \\x4c45e1529f3dc739b3fed7e06911fa0b1b0308a31c50d9923b1a87050cca7d2dd88ba280ad48279562a95102e60fd484dcd8cf2d98912925c0d4bc9954138f01 3 0 0
-3 \\x1f19283b63be41e38ad6a021b9a5382fe5fd2d8c10dd3fb584e4f30bc5e91af57a7d6c79d8167b48d09cffbc6467afec06e3bf42d709eb2c061aec51001501b1 2 \\xea573d029b9ecd56715daa8b3fcb59f535fc2df9b346969b4456fe1dad9511528a1dc4989c0ef171ad9904cd89df5a2edbdbec405ccfeb9456f54538c6453105 5 98000000 2
-4 \\xa2d1f43ac067bd1e9d31adfb6fd9b599dbeded3525b15701da7559fdaf0aa9d2db4adac05d9c208605b08797eca0f1281552f0c2835dcf03442758cb720a57d6 3 \\xf512f631514aadfea3ad4fcd0835a57a598dba30f78812a6eef53a783f501cf593a2c96ec2ffcc71985ab43c1bb5959aa6cd8ed149babfa7d145b7a7a302a909 1 99000000 1
-\.
-
-
---
--- Data for Name: refresh_revealed_coins; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refresh_revealed_coins (rrc_serial, melt_serial_id, freshcoin_index, link_sig, denominations_serial, coin_ev, h_coin_ev, ev_sig) FROM stdin;
-1 1 0 \\xf2880e36ccfdccee81c4e9d2c8662b92879979796884fefd978583a2473d64ae89a9a80e9552c87a3cb8def91df4ea4b62244620d406880864511a093e3ea00c 335 \\x22d45c9e7e88b0a9a7e4b1cbfe0e965a33c4afa675eef8b3c2a09ba7aa10b61354b69f5e2719a5dec68c199000ca2d4125bb730a5eca70c2b55cf38c1aa5664b0009dacda85b8c12563f421b27750d8e421df3f64eb0ed3857448b921422f080f9824e45327263ba5add1e1437043f7692e245f1387431dbabfe15e12d38e698 \\x119e28ddbe6ba1b3eca8b7e45b69368f6f3b99c7ee0279e9da6ae12ccd35f099daebb90df0ccf848779676952e872ca17339008882c334f6026169e517753ad1 \\x0000000100000001072e12ac1dfd9a6442ba6f74388490db3951bee0294cb905016f42b41e6dc0667d6629195c7adcf3d66b00f2f911f65c3f90874897c8696b1df9aa58f116a9897580aa533784038fdaa2ba9cc8bc30c130b98b4316050fac85532e82aef56d3369a0d464adeb2a3c09243283eefef0fa9463a216162831ad4f77138b41f5fd70
-2 1 1 \\x6a84f7273824f30d522b74e8651658ea2f5dd550137df73cfad688ca0e190569085a9a1b1658f52ec6c3252e56cc45e4d11e4b08d1bb31adfcfc692466201007 256 \\x81ea9a4b889c3410d6796eaed4cdcbcc0f842432a0a5b57b2f17ed90b9678aaaedefdda08f60b88b59c20aeabc8341863c19a4b9f0cf9edda9d4115410b746350b04fb9d3a77920d2e64c55a07bd873ded9dc63b5e88bcacebbcad11e730f1b140c242367d59ef6a46f82a8703b2efb2159775547dc8655f1e6c9e8ab912bd05 \\x5dcc9e9d43e96f63bd755e1b94afe5d2244c4a629c3a0756e74939c2177119bc194930ce86b62c8f4979e01794a62d6b1e2a26dea107ebde0ee9d3690e7dd84d \\x0000000100000001a5c9ffc713f14f44587696e327897d8c223f4b74d04ebcb735da5be562d36998030310365555aaf149d8c441c0976d250eb6fe85204799ee1f4d374eeffe1251daaad1bcc8f7e1649668c26f63309530fe7386cd115e3399f8d26b910d3972edcc88ba23ca9ca46cc891f6240d2205feb790b8dc52df43bf7f81474dbd799cf4
-3 1 2 \\x39ec7d6472e4e8613b9a06a8dbbeec6db736b8444e82180d654b02e000c4c20fde1665fca526424290220a14838feec76fcc81c4dd7cf9c7538131ac4e96bd0f 368 \\x7c7a7f8af35e8361ece8de5b5646b623b405b4aa2344f4736f56af8fb4682c22b26067984c7f0a49a16d85741348fc64c995bd2ff7dbdccbfb6061e0b9ba60e2aa513aa05114de3221d9e9085f06e7c82948f2b7710c5816c980fa8d056e2cff1b85440161ff4e97e5d64d7fc49a969ed8d0c5d609ce46de1e0ed2511fba1cc1 \\xbcfac3266f012deca044930c0dba8a5a59930328add12db33f9703c509fae32f65ad0ed927bc09e2e5a6c863c852799df42a5d55639d85a2ae65654e78c9e1df \\x0000000100000001051f998561da7ac6f09910568282a816058c51e848c5d2577a4e7aed5a7140ad1a09a798cb6848463b21ebea04369f38d2fa4083d9db78be68892d2e3f86179ed82e8e1e7284506687bfce2b53ca9937acd3fd02feded0e9bc9e3758abff8ba1522235e43f42f0df5d77f2438341c50b2bd3d782aab6cf4c48690e5c8a665ca6
-4 1 3 \\x16c5b6be20259eaa35e152b45ac96999963565d34eb91c3d733a35fe821cbe1f53daeb42d9b7e704130db54b9f347af72cb96a563c6eb27f5580fec1ae32a405 368 \\x92da4d62df0f75264c92d1b0331d13b22d90548b80e7905ed1b2b9210c3331f0271acde5ed328e1ce4e94053a2e1a136fe4d7c7514264a78977e6e92cbc37f889c0fd1f11fa699e327cc02e2e5f921ea5b8f0ff3058837b06b5afba6f7c440e2aacd9c56350ec42be6494aeb3513b7f0eb9f1ed26de12c6b26272da9a7fff257 \\x808b02c34052abdc8bdc0e5b3580ce85accf78e19710222530f7cb74b716352d653e4a49ce71542738d99e06ed934796bdb63153d63b52bb22e5da790cedf1dc \\x0000000100000001a1bf96507f97ee166194ab9a7abd1fc2faf31089b5d709843e944f81e58a1ac195e60000fb1ef9a8faca28871f6979fdd4ef62bae8eb7dcf29131f5abd9c4656bad401d0d106e0b75fb0e733212adf542c5c1da30e03a5f7c26a769bf2db8e3a57feb13431a3781a1a75a7707b7e8476871cc01a9c5e1b53fa50284af2b07ccd
-5 1 4 \\x5ba35ee7cd244f217ffdbcb1d9366ae81a11ba36ccb7b55a6e68c7023174c8ed4282fe9df36e3def0480fc96eb2670df0e2824e074db2c75fd55d76696926c09 368 \\x4a652976276314eb30bdd898704d957598a3b09e711c40c07e0c1f2a4deda1de16b6c781940289904fdac0365350b684e2fa22d87bdf005cfbd7e732d919d91821367a96b203ae43380eb24d4f7ac04154eccb76c219bfed8edfd433f0962375eea3fc09614afc6d540a748c1c15985ec9126f6ec6d579d5b110481a7b1fb397 \\x1693f1bd009b716c9d4d6936e65f19cbdf79993f8171ba886af6b422d9f7af948a979fd10e8af6d3b715a6897bdf30148d07333da2481e520282c18b7ae5eece \\x00000001000000015255d1e7193298ddb1957e7a4a835854756516691ab13f9bba1af6cb90643945fd9124f9ec70fd561b2a08c5941e923160b158acd13a8114b9f495ffd4dba0e1197cd31422958c0ef18f674b9f52a3c3d938061903cc7f9408af39fbfa0f0e4326a6d34d555901bae99b06c3ad3d6a82d7cdb3b9c07a8b1e6fc33fce9316ee32
-6 1 5 \\xa889c7fbbc5875092af25a0da92cc990e8545ef09a39a36fe65c8f8184c848021bf5f83b0197c6fe306a61ff8743a2cb51e62f9129eac1248890bab654eb4c06 368 \\x0ce4b3045b5e07efa58f62d9f723d553c0387323bb02d1a249855b4802f82b4781593eaa7ece06db3e8309ea16f09fec14e471a52716463cb723097bb22b60b6be2c531333b15e2e240354b95147aa0e9dcdf37a68388b1e0ff106622066316abb18c2cf1bdb8f56b82bc1e325e3f62bac792f987005cbe9aabd29ccb45218ce \\xd3ad734214a32762e10085646e0e198b5d7cfb981880d6677310d52fbba0948d779cd164df16e88a5a78b42f4fe7f22a5aa8748b6c066af3acf34b8089c135f7 \\x000000010000000162ddb284d57211a5ae9a92e6b3b9519bb9690985f1c42fbcb447ada3d519eeeae3245de54acdae0c25dc9695f4f128f7c3d55f217d7aeb79e16458f850738e4920ad3eaa58bb644dc0e222f3a9fc001db203a7542ab730e3b23e760e6c696838a23c7d8fa56fb1e9aaface379a91ad6976375921d0bf39b9f6d07b89044904c8
-7 1 6 \\xc8cacac1621f9fdcf3b357905600c03d512be10b819292d18474594aa048d62ea6c65aac9b6119163b538633f6f62f57597267c94e574570c2b3823834e0f909 368 \\x2ca2c7e9132242a7e8996480a64f064c17dc1a6e1263793c73908187573a0f29ec633fffea16bb2d18cc58a808923aff6bc1366e368169adc68d22940aa0f0b775c682326cce3a41cfc40af52e5951ea82fead60eb70ef3f1b6c93ee83a55729b1dec3574c9e907e13b43986194319f676a2dff31e9f88539094e0ae13f40c46 \\x4d636d0cde1eafdbd9b42cfe7bafba82deb359848882bc87f755a8f8837b58ff8703e414ce8cd1cc4d45952937b289fba68bb153bec14961f6af48d8813a2540 \\x000000010000000148dd0ae4df05554ce54f1505230eddd822e7e1b12db719c4f8a510cf22a848fe222d917cdde1f045c85850f44f27a103ef2dadc8b9235bee2596e09c8e32de90bc45e77a89ad3947a39bf83d4eecacf08067d60bd9a0d41d42e9b12a7881d446452eb033ea101d94bdb6efd558047f4ca1d8e2f327c0d1e57c9a1333295256d1
-8 1 7 \\x472fc2006c549046d2793e35afd5fea71a829a2574efb6878794e2507d20cad2d83c721f267a3532b03c2f8bd4ca5259be30d4c49e5f665e9efad4db3bc9fb00 368 \\x3a8b2e76358c1ddf19d59240c564724b84b5514d7169ad2c2083809f28fb7a4274a42b270f4fd18fa7ace54cd9b49716857f942a7eaa462ab64e9176fd88a7da567146d8a87c00f9c048559e4971b3a7522349981bef774a07efed7ba866b6620789757f5988732b4b88338fa367b755528c2aa1776ac5c213a4d952a515c3d0 \\x2a1f021e5261a149d96ff8a40f13a005ee5167389c7c9ccfbe105eec31363f29faf6684ad7b48457887dea4da39f35fefd40a11261eb3df842c5a13b6bc2c18e \\x000000010000000174f8635e95b0c9be1037af54b57752bc6bd2db4ab7d235d4ede6d58b0d522adc03f39253fe80a8871c529021bf7cb8f5a88efb1f4b8d94b705665b208551ab28f5740bc6da17b62e16a325ecad52507110f1176fd35f299aea2a9cb27d91ffa5653029f05f9378cd25e2e7c68fa3fe38cbcdaa0fac68c75cdaace852b5a0a950
-9 1 8 \\x67dca13b1d951a83c86ff22f0686d2bd7c5747342538757ca9942a64c1af3fbe1c6173705cdfb45bfd22de3a1dcd60425694eb2aa81364d3fc88d2d9ae9a9507 368 \\x5c56a05add63d00acaa706596284e383f7485e5ff70662043859a8b92d9c9b720c6773b3f8ed81072d963bacbd253ded39ed9cc9afe09d0409f853c007083d27f42935021a1615374f2ace7f1dfd1c7f4dad67b8813a23bebff8d0ddab0c5cbb69599d7d687172f8bd87240a1bff4d56a29b30e002f9e1e5121f19c752cedfdc \\xe46086a38dd0ff9c9264a10cfb141bb6547a985dfea30098895888c0080f8ce8094bb3054eec8fb1a3df4544a057838c759459cd861b3a4aa1b0bf46742078c7 \\x00000001000000017815d242a4ad1b7e37589e704b1be350df2b6b12da1623aa86a4f7f17fb190695eb5864803e033e6bdac8267ebaf235f5c47009b4179b02b2261aaff27945249c30baff37bd6d0605c93daead8ac23d22815c0c3eaf9dd5c371e92816b4fb069266302db6c048cf019b309b782eb0bbbf8d2fa5680c1f658bafbdfa82abbffd6
-10 1 9 \\x6a95de9e3a6a1ead84930136a18576ca7308d750d05aa5410cba3c24834d9b9715548d3ba1dcedbb35a590cdf90a50cc82845675d8e38ddc75ebe5e5e9a2e009 368 \\x9b1e2cf20b351b97688e33563e0b3a37176d06d5da232c8755356bdedf932c8d069f713bb8c71b926368782022a3902c082ee1ee459c8a3c75829bc23d1edbdec75d935806c0c6ed04fe1dd04ecc0927848036c62f5bc47cec6c2f74fea0b5e8e193ea679f7a1a792b19941066e7f2d8c410566700ba1742cbe1a39b5e248e07 \\xf1555cf64696c0719182e722d86acbeee29acaf2b468d4519bc5ef64b4d8ebf120625131844f544a9ddd7be7909b0c3991123abd738cf0f79411eeeee773ab37 \\x0000000100000001af0cb80880ed43e61dec435a917bf5b5193515c173c71b3d39b257cc6a43319f78dfd41903356f2b4134849f81358f2b65e341c0c1810c18a64a5d59c212b41bc9fc4ec108f0b0a9d80153edf5239d0eca2a58cc9fe2c4c34b07e892009a947263d53fc7aebce3f85af42a842103404f19b937850760bbfd4c8570fa375d163f
-11 1 10 \\xc171f023d6da60d7ee43ff0f55093b8744d0c354bc3092d378e78ad3dec520b60781e2d5d9cb657a536dbc371226d4189b9ee50609ef4a37b51aa02911c8470a 116 \\x02e0bfc900b766f21dfd270a16fc61e2baa4c42eba54070e23d862b069b306857352e56a8427d73993bfef9b0b876a942750ed9d614e8ba2a2a9c1ea756e11bda7f2f6d13922f6a4bc3a44fe0b1707146d252a10846f1c7f00110460c717bc15e81117e4279e5a91e53103f64483bcec882ae4f4bf0d9ebefaee3ae9270746c5 \\xeec2a4de607073ad153c325a189961eb8eb62ffc88ae42861b35403408f679e3c048a954657dda3e095286f3f4c2ea6e42df2e5e1263f77a583f7c6474085129 \\x000000010000000181452f2fbea8d882d5581a1e6823b964db62719dfb8287eaa8e8fa4d8587fa495d2e22407cdb30f75c2bd52ea4f320a82d97ef9f0c7925a0242d1ca19834c3985f383a6754b4586a7e33fce12975b38add99bc1d58905929ed93aefcd2d27797e129550a0645b640958497728be3e0d30d59c03fba4da9809549c32bcae56011
-12 1 11 \\x13450564c3b6a6db3913704b2cd7487f9bc7e196bf9693ca4630f64e953eb6477c9a9b13e7d37f530deb7d7ef2454f37b7ab6a513d730c2a5441321f6209c801 116 \\xb03892d1026507aacfdfb65eabb098c0275a15a41d0d46ce47eadab0f5a50d201263872fd560eb5d74d31ddb6d49dce7cd481a7cc93236d3b52059200c36cc38833958ef4895131494fdd8b8f34f03033b07c35b94b27481b9cb5b7a1cd144fed837543b10394122ea94a5f76b04b98ac8be78fc6ed19559d1e313d5ea6020d7 \\x3aff9c2c07ef0e67a4b312c598cd5734ae729a0f1d510a3d4f2d076bf18734ccb6c0982b5027d53c78dd1df8d0e6645c79bac4d7a03443bfb944c5b0a9c0d2d7 \\x00000001000000016d1fafcc1da5ef8a3f8a406be2d56dc9a1f7819c94cb17181e5daa3bda503168e624bb7ae19e8d07bf080f03557022b724256f03b50e377718a6ffa2bdba20832f1633b074a437bea53734dd2e5a8f84000a01e65b039c612669977425f2fddc743c781334cfd34c18d160a639755bedb772c9462e039b13df8f219d7a7e9944
-13 2 0 \\x1e24b8b2832d32e3d1974bddc12428e9f673c47a0cd874524d481c747f970f8a808b7499db70dbdf51d234876e03d3aeefd39db47ce7ccf3cb60c3ceb2012202 335 \\x5a684737443fcec485783bd173e5c547f77a204a687cfc10bd30f7a912921fefc4180b0a77d6cbfb835025344b147e3abb33f5da76c050982916ff30dc7f74755514f4ca494e28c43a412a598433d0cee87b27780a89fe78972e885d285c613cd5b02f543df03269310024089715b8f3018e51dca84fc24d3c9c8832eac47dc1 \\x447722b5eff26598f8785d4124d53a06c5ea79881b6b15e61bcebd6057c0d6439af3a70c568d9b60e9f1d05f93faa129fff473e07a94271ce962c10ca91af50f \\x0000000100000001c4eda265b07774d492d43842de13f60c7c3f4d6368127193e80e912de3d7cb2880d68246920d1f6dbabefdd415e9628f1f73e2be67d1f5558e46a071208cb819cec7e1cda7703a5e033b4a887a86517fe95ae869b3163f36c51b04e8b56822728981830c98a2b023f8c36a4c7b88d47880b9ae98578e347cc1805eeb0acd83c0
-14 2 1 \\x72e6079e52cb0a069ac54fd9e3f122473407783a0fa0c09d6f8b38e96e804b867c1c0d839b63b17790e357f6f6f53817731bc7149a1d0d21ab9f48817498180d 368 \\x10cb6be18f3028be2c78335419329fded3f5e8f881e3568528500dcd0d5af6335d17288fb5c749e86041bfc349277f3769dd2de751baa8fa554ae977f665e5a032b97d3f4d9bacbed512c594f82b56b0036da0784e77f4b30590e029b4230c54cd3790e5ff9da16c8663a7a23f2866bca87f6e7bf9b82f68f3e81a484312dd8a \\xf2c4a8911fb7513dfe3948522146d17db7fcac64f7139657a34b93a7cee931840804ee2dcac5695674b12db8f8b202ef1a73cecc23e5d6b8259cf5750f1214c2 \\x0000000100000001145c0add159e714da4665ec1b955460692582f6a96ab07f1afd4ec4f5695da5cefdb1643adbc236aab79b513e8ca280caa05a3970ca37377755da4bede493a6f556734193d8dede76397897aa9db5ccadd90e21a96ee5b7cdbc0035d992db3b4d9b1c45982ebda5f7afbdf593834a1680864d55e9f1354741ddd4a6aab307d2c
-15 2 2 \\xa4a0f606f834a3cfcf86c4c9ada2ed756d99ecd9052c728f167706ef5a426d6004824632cc57197f46f285bd7d5cb8599969b1a2089453d2d80e9c6824c9a50e 368 \\x08c9fc60f6702d9e613be742c4e6cd842d14015e1a9c170542b7dfc5cc3ffef2770c08425ad6119191d9d7e84401e13a117d56f85f22ea15ea0dd966cce88e8a5083046a97f41c2c94d89cc56af348d1bb19410a1f5777b7277538870e34dc74cadcbdf0af034c4b5d6aa1bf01dfe22e3c6cfbc05dbc3a29aad348c0a8b03d68 \\x3816d9a5f0f942d392ce2457c8738d5d418863c7ee7b7e141458530cb2c482e260f107eb945ae6d0be19067b6a5ccff7bd855cffcec8becc0a97236d2baca602 \\x00000001000000017b110cae2180efd15749cf32aa8b1e4ba771e5f5c7caa288e128a8d7a4ae1a738b7ee11b4e69933db71a1ca7bc3db142e56ef0190fa1d32819f76a223acb35a33d7ff420b005115bafaa0c99230c7fee36829844d54caef6ba7077981a1f9b8eae44564ecfe13f5de625e401f4598e6e822176f8759fe3f0ef45be6a98e05f9a
-16 2 3 \\x22d141334b2f1fda2082adfcd3d3ac9c7df4d08b9792fe8a645cdd001264bc1ada3ddb722d08a2489ea0c687d296d33ed6bd6a480aa221e3858acac94cf23602 368 \\x99d3f3b0a0388fb0eb251cb6dd6120ae975b671cf66ca8cc6bed8f0414aaf617abbb81be4d4f5541ad2df61e7a6e5a3a8d31c106a7ffdccfbff5a3e69667498d2280b665c2b45581bbb804d35e9c3aa92d2ffad2de1827e4edeb55c601133ce6ab6ddeb33780f40197f2e1b138062ab837dd9aa0a5280744422aa3a9244c05af \\x8fe8ab0c2ec82032c1153a6b87cbaa726e983cbcb0395fd5a6e351a5db9b399617a1b91131585388dc6f8337f57677071946f3846c8ee37226620091e2e78b0b \\x00000001000000015274737f5cd05c853476b616a6fb06f5f522e0206c2dcdfda1f749602efd3f1451c72a11b095e4aa9c4da2747a9097f654404f86753d8ac2b19e074389932283a93da0a85f5a0fa3b66bfa87e91b3039c658392b74ebaf84196cb08f7d2f39a67835d5fdf75ef4a545cad6036784e810b0f5e13d3aa1d65764457d2213ee302d
-17 2 4 \\xaaf2015d137c0954e9a84e1b392b9cb16697e1a20650dfec2b4f6af001db29f41a43d7bff9755e45b3408adcc7ffc233d6d5e6525353622167fd39ffb388b80e 368 \\x6072b76bfc5be103687e1b9904aca7b9dbc69836953be86f79956f549bd538f0502bc12c0d68c1c26402233151fe53f9e890677c0dc58af8e54c3a10e24af25d64e02f6785cfedda71f5270b5c5adbecd12a3a6b2abb14888cb3845d4304b07361f59f20fa7d33557359fd5eecd904a31d19487bc638d71e6c1d55f8bc48812e \\x07cef859cb4ea8fa9ca7a560d17786b479ff17f20494c726a8dbee55422312963f85ec70980948c3de09804fea4a659f0e9a62f4e66eef6f5d473d1013a2a43e \\x00000001000000012fe678b29961e5eb64d12fb25bd8c4177b3a7ffbd8a9059ecf8f133850e5bddd2841a92b8aa65519d8622fd078747f2c9a16fbd2ef7a8bf3f2ae3b2a2a7af4981948fac8b3cda0e242db41b807deaa33accc482380c3c35d610243d1c6fceace01738d54193df6142b208de81db9f912b79f8b09d5a3e8d2d602f7a26850d425
-18 2 5 \\x331366087f20b7ca86478be6a0a24ecb8092b06cd429e6b521119b09e1401ab95e90352182099cc56abaed51d3c0bacc377af5a5bec968115fab61872c1abb06 368 \\x3103ab13372bca29a2fd6680b7d49e9245e9310a3515c008e2cd6a4efcec0a2447fa112e56f264fc6458313d8812d80f85a1bf8441e2740628dc590011df6ef6f5ffffdf12d665e32b39b9d6fe2e21b2f98ff23ec3ee89c6b19e4f78ef8632a37bcdea490db060055fbdf4355822ce6667bf34d6f4b4ed9a3e2c3e272301433f \\xad3533251720d002e890a0efe35d5b615043a80e83b104a492bd9be45401680f62b4c9bc6bf9cc276f857fb67a8b33c462d226b568b24696d7b8e9bd9d860d4d \\x0000000100000001c719ae714cd99f1fed7899807f8deccba06c5632a8f74d10185942ffba9e14bbbbec89b3484d75bfade2b1d04dc0030db4ec84e29c85a32fcdfba042675bfbb6f7e54dc4e78e57b5464c7219df56806caad2b24b1b367e4bbeaab12f6c27953ab2b98064d93cbffe11b5f456bfecc68cdf5d24eeb42869233310bcfcc9f0620e
-19 2 6 \\x8e4937eaa627b53d80385cc34344e490722a2274033305c0659fb4b10d6717d74e1db3ef25a90fd9421bf09034a3b2c14e95d4633742495f1b6d90cede31020e 368 \\x7c43447716eebf7bf627a4020f30114e52f18aa40d42f6acd5e3266bdcbe9152c10b2e1d79da8bd281c1b0ea2eecfaf22b4440ec740790fb09b9ce38381dc11be6fa0adb97c31edf376055f7cd0ee08ad1963d08f520549c2502cd51a390205b68da1af6e33c2ec05b3dc16e36b8f5256d30b91b42facdc78cdbac64f3dfce87 \\x91a12d728807b48bfd9fa71fe8e7a643f5c42d340e659322796dff76e96e17e1acd6e408131214cf879ca38cefaa7f951c52dfd7655c1387b876f42412d4f1f5 \\x0000000100000001512114400f55a5160627a4f996a6185b9490e10eb4862dd2877c9cce720c0473452f2425f992ce1df08ca05d0c958cc76bb3d7aab93d99de49f933c76a5b2cc4ae43e031dfc71ae18c4afe9481f701c58b9d870f92e35896d345c2014b4500e407ae9cabf811e66499196f95a1b1414c4799f024c230b5304530345c3d708ebc
-20 2 7 \\x3f1ec798fc9124b722e41cbd663dd15524bb946f09ffd58bf4ea9dbe2970d94725b665d63b788e40693e09986591151094d3ef419c32d23f08f53d2e2de21704 368 \\x72b2112c7927dc31333d48a20fbd349383d6e890e1ea442823e8823868a11023b69d530ad5c67dcb9b5e30ffa9d4d9943ac9fed38edb289cb8d3e0140b140764cb15e355a1c83904066e295f2ef0d4daa32d74e46520a98e59ff63d2cbc73653e2c0fc0cf9cfe0a4a272d712833be7bc6e72e475d726317cc330b9b5cdd79d7b \\x1ee3f357606fb436483355a09a0b89bb8b22b2b543ff28dca5e5df074c21c4db8ab6d5527eded843c1ada0a8b2a9e5347b9bfb927dbb799ded1050039e3c320f \\x000000010000000108bbaa39c06c984f4d0b093f6f89b1801142a00756f88c5d779b73af12842389f88890b48d232a93f88d8a7bdb05bbd378c9ea802cd62542d0c588b17f6a70580474a7eb01354f3a68a1e5099f0857f4cf189c26d90bc8e363e699eab1a0c1bf655c86bcecefafafe6c7f6593f79a77a9a31ba172219b64b072d74ebe3e0311c
-21 2 8 \\x41443776765d3d47cc9e68aa4543f54fe77de681ffd435527ae8e8c868e7e1bd756eeb0391479afece8f2e2c86209a5a084135bb2cb98d475236fb4ae00da503 368 \\x73c2cea698199c0f3af83cb1dc80d7b6f1ca14b040df10a71ba5afba26e0cc68455ba249d184f7e59714188869edc01d48ec386c340f51be41b74d8e7d1c3751f2f5e6cf457a7c7aede5f77fd2f9ecef7f3d900c4e5953f8aa83aad26c7a12e00405b3e1bdaa5aa185292c070ea3b20e953af3c4bb33e9078f724a6c8187056b \\xbcd3fa1d22e830ad7f8b0bf4c1e9aed37b7f30166a508b48234dde147ec53b2404c487c5802bf98def93e44d34493d0c271536c02690b2c44ba2d4c9d8fd5599 \\x0000000100000001a070f2055f79fbb9f2dec9b181750357e4cb653ce2cb5d5e2c4ece819564607e551e064c3a8ba9947c688ebab77e54740fdbdb6d258dcfe40fc181bfe8285ca544673d84dbb4e1961fa88e7f46e2848acca8ce376cf56aa71f31b2074ce608f53a2d3960c7b265cc057b8032e2b5579c33477c91a7b8e2ea96d9e422240fa6fd
-22 2 9 \\x8e7742465943390a9b8890928cdbd4d46928d5182d236c0eb557528df31c8fbdefabff0ee77d3d79bcaf518cdbac5189b11bace7d7ad3724971272a6b88f5708 116 \\x38dc7b948a8a72666bae63ea5fefc4fe11aac9d090c34b5b216b0011103b58a273fdb683a00398955486c8ba93143bb491ef176b21e2151cca2c00b22ec4272e46d63341a35c7da478b3a038e7d5a8a181ebf1251ac633e10a94de96a2e48fa6b8b4037eef640032dbf5a2cb889f9c50a97074ee109e1144250a9ff1b5ba0fde \\xcdcdb71b1756e4d353a545983733b76fdf71b18adfbeae2e610b8bf7a869faecca9625c86ce06073beccaeaf602f5209fc70dc64f1d427fab27703f0b43be947 \\x000000010000000148a78e6a08037a53573203960724a642f339d849503d9780f69255ff4bde62c34a9e9417f9288f21bbcb5c934f869e5f7325585fc1431e1fe6bbd0da8afd0fa1746635964ddeec5caa8531ffb9711669eaacf79c6ffc0268c9bf6fe295cb7ff182569f438fa1a5a41527fe313b33e4bbb476a7f34cdeec435ce64bd3f9d466d7
-23 2 10 \\x5ce15fb349895a1ed1039ec45f100bd31aac314460cffcafe987270a14fa923b59ecc1e0254ea80817bc0eebba04191c22773150175915a76054915a826a8102 116 \\xae644c109ead8f0dfdaa9f074467cbb3fc350b4db5b88c32212ddccb84ffd023043a8feb37240f5a4df39c3803dcc94c573b8e03bf31b0addcf232e48461ce5fc7e9a47117fe61da58bc74413e0645fae00c751fc9e8f8911561baa999ecb90e92190793e86aaa00044cfe6cf645457e2c1be6aa03b7d4fd3d1499fa5c65b10c \\x9911794d0f400c6733514d23ea84677740f3f8677ae0edb7aec08562fc8c06b3503066a348465484f9b082c26895e7a90786a4e41930a469da400163590a2431 \\x00000001000000012ef4c544a4cfa86f3aa9abc145ae8ecaee409b53db4cf6fe8d13559acc0fc6907d31d19ecf3dd62fda037b2941edd47fea08397028e9714dffb5f219f0cea4b7257d1c21385856ca41891e7ba97a6407a50e4816338eb80e93b6bb98f5424531ce4cd0f907e069d6c305f4fd0fb3d44123a4482054e258a5e172f747090c303d
-24 2 11 \\x0c2c23d286690a25ec3812c3512f3798f7d5eb8ef163290a2134159cfc340b1cf8b13eda7c4fd2f7177c5105f8dce99ff27872a98713706bcb25f6daa819990d 116 \\x2f44e015a8f285c87c84770e9325d53cd1a1c9fce13d1773e5d4f62cfc0185626008a580d72dc136ec0736c1f817749bfddc86d8d2c8d623b5f844918338dae1044867da3e1aa21880298f787be706a2cc4f983f2f09a63be2e2775770a9660a415a65d04df1593e6f1696e0000478834123527e39df0cad589ab3f000b7239f \\x5c593b82a1b32ff7cbff1255be2f9d57afe176cd24c657a515d538c8ef145b42dac34b9048c67dd84d8694bdca5b554b65d7c50a6857789c47a71019c01cf29d \\x00000001000000013176bea3ae84456d7eb267792e044160a226894c628f8e55f3841e7b85c153f61c2cead0075f8e21ac458432eb00de247429c61b7b7e16427c24c26be2c1678e998729ea2fae2c3393a3005a8976292d6c8d7564b5be34526aa7059177d561e7f612405bcc870d10cd5ae3c01c1796612fe63431329c6b89cc56f61921cdc19b
-25 3 0 \\x73cda8e8d3f40b3168489655605f2544f2b696a78b5f1c70870eec9c0e4d6bdec5213b0df209fb6527f12cda0ed236f21dd28f3d7383b556fbf81d55d4512705 158 \\xa0cc856fb42b56d5cd63137cdb74da2d20d6d4d23a0c5fcf3011cf835d8fd9e16836fefb411c2c4216c6732aba55c6562902d9417a081a88a9f8a115060f1edf6ac4730c4508b496f938bee03b94214fa0278917b04d8a0bfbbda49770037a73d76b9cc3a8837552a70dc9526d0a87bfe84e5ac5ac2240d3ce16ef4790e4ad6a \\x88c40ea454dcb235cd8020f1702249cdb4971b39d0417bd19258a0b0ab7e60da1764e01ac5f9295a1d9f3f835e0f6169679679f01d0fb98ab4fdd831da2ebd49 \\x000000010000000153345c46802e5117259acc126b20d73be4164378d52913177ddf2bb47a98b3dc72515a5b6f7f86e946635c1015cc4d7c83abbb905bf23087cf7f8d98ac917a772df348b43a91faa6b4a8886d3335ae8880b81f0bc3a23f52b5b9a068d22fd4f1db1003236fecc16af74075355419a8e43d64320e3ceeb9117b290f453172a132
-26 3 1 \\xcdb3417b4663613bf81c8decc65419face9fb04a82b16f1febbc61abf6f50b1b68f62a140cf3fff7f9fc93fa99219c9e2b168f0d9f25996a573fe7b79e162601 368 \\x8eac62b94b33fb4542ca03103129b3c9b6ac1c3ad901fee2447300addd2239157f04bb06738c6a88e35ff0c2996d030503a844e050b3a724a59091a89f8b081e3d71ec988c0e52946e4ba00fa039e2aca7571d444761023bcea951bb1795521cd84773aaced5dcde9822c8a6aee20ae0ee53eaf39c65075e036f29dbc29dee3e \\x688959a0e11e02e47cc91de511a885e4b4bbedbe14b8e456d52673802db6686150dd82e997a92e611dbd3b81396504ea799c4ced174dce862019875443c0bc6d \\x0000000100000001348583964ede31c2348aaba0e888706a8ee4cf4eb0f5bc8c1915ee49395a01e0c1acc70062524f3fb8e982dac3374ed936bb5af2acd59c1f33aad9beecba355923fcf146d14c078ef0a2e58ac10cc7b8bd1b9d62c04ccc4e962b554aacce54058af5767a4cf52594bde80d916a9ed7cbfac745db70be220601551a9431cfd69b
-27 3 2 \\xc1bedbc4ac24e402fd0b27f729cb4ac228f207f1d4b3489889ac1d7fb59fddf0c4cf40ba6795523ae07eb121c8c62fa81b2db9770fa79a9cb14250623e34e508 368 \\x56d0c0bda416cb4c1800fd1cd7d87e2be70f33b4389e8b3eb0ae9bb015ab6b759868c126cdb4d3b910eb00c8c05651dbbbcd87a896c94d17dfb6fb71ad05e0175153c145f3d3617051955e71e3b987aeaa3dcb921257f25cb3cb4706741ad366df8871b579eb5f2468941b9a51ab471acb717081d3e0f8b908d33dfdd55970a2 \\x31ba652f114aafda4b26abb723bd24c11a498935e51b5b026a6563e004c4627a00637ed14386ee51d53267896f3fb70a977b1050311bc4c8464d246c64ca3c14 \\x00000001000000018f22688533eb19c0110df5f3188a4f13ab7e1f02e5465ff1f3ac3ff1bc5b0b0637f44c88643b9b16a8eb2a9d18f8b8ac697a22c0db87494c36fd49bfe58f78d0566e7386df4fd9ccbfeed034474e2acfeeb41a96a3520475efd57d09f4cf55f106df3abb7c2e03bc2359f104681d42482e687d03a1a2bfe790add16176c16f51
-28 3 3 \\x2064c5acccf2ad408c11c03d7e9a2c6b544575d20f8ae87e83adbd57ef05d99784aa66a3fb27157d93046d08c64c3f9796955a4c8f84e40faef0c75b38a5d806 368 \\x472b8b4884de004c5aff5e6fd5e97ca5f94661069544f274b559582c40dd810d7d11d6266e80b14fc0ca485b0f699844074d45a4a08608097a5b44da33b2318031c5e698f64848e8f98a76544c26d2a618f9343a03053f554661932c9eda89eacde66ba7f80c0e3e9a8f57525ced7e51a56fbadb6d06ca2567cc8d3292c599eb \\x9e30207cd3972b7f4043db2437557c496e3db549a07dde94f7ff06d6874e7fd88f93a2d927cce6f358925e6a33dae3532800fd437c63fd63451b7db5ff8c3a4c \\x00000001000000017a6858b98b47f929a749a693423c117076a88ba1155b12e6ec834572b6ed15adcf172cbf88ffb847c2e913b481a06d797a4ab6b5c4a5442f8c60374b5516b3429f0de1892d9c3bf13751ea10746a91384fd71701a7f2dfd60e1ff91a8683c0ef52420c3dff9a99e1dcd7fcc2a7001a8d10cafe3a62d3952483b209ef32bfe698
-29 3 4 \\xc8d6dcc2aa1be1708e341525aece77e9bf06945cc2c967d64bbbd0a3641d78f706e7f2eab34beab1df31629787e459c4089e14bb2b84d8bac2f6960c0ddc5307 368 \\x9070661732864b8492c9c0f822207aa0ad3b0d88c7dc6765512d2fea23c08b67b9b432e04a2583056d25fe81c83bcece1a77c0b4045c166ba48e9259b3f56e358d9191b90a4738e2fc2fc01f6c821b39fce44e09c2b1031f9db7199db49b5f64cc630f5d34d270a509cdbeee551e893c23ff65c2a28c7ff714d0f583f60fdefc \\x3f39164ca6c10f66c948936999de33afc7be60eaa18d71ce4f0ea573ce75dbbef5fb355ebb2ef757bda8b663ab7af625d505bfd02b91f093e5e7f782a3b3ea06 \\x00000001000000018a6e922a0db444a8bbbc0e8e2f0126b63834273a00ea67a44851773bfb01f4cc0f1508bab78d4e04d3e13c38542e4e4c168375343e85ccfe29fa9aa17a03fc05f8db5d82f03e9eebe8b88d9cf75075f7004b997abb32a5f3b999192544e5ddbd6e10f50fe7b8153b612ef9a8f4093751387969f803577814656e895414a58549
-30 3 5 \\x91aea62c2619238334c3c3e6fb3f57437269cc21ee84b520e93221b22a8398b583f93566059ca7af098603230dec84be28aa20960b7a8f42c3cf42c8968a9801 368 \\x173eb10970d7ec4cdbb3881c95dd78fd9efb873646701eff0afcbcf507a584976641de9cd1a2675d1cf0b758bbb0498485bdba7ec482806aa88c92892121b4873aa660224c35533e62722af842d009f39db5605f3d452ed70db057266caf60b1b700194eb31ff5b71499b2980c0bcf175339e6305f947ea390de535b475f9e2b \\x6695aff03ea32ae662d4f89282fe5cfdea2294c112bb22465520f6a0dca785ba384dc37c44b5e91b60e280897eea60818f3b7f50134ccedbfbd6ffde21b10234 \\x00000001000000011a10216359360fce86a5e22155fde0d223c157b240be2123e3e6e354989c4a7c33ff028e3d324ec2ad82cd720c28bde0ab1f489fd977d8e7312c05baf23482f5bc2b3511f3919ad48503e59720162f3834489c6b65fcab6655ad99c5782406d6b69ebc59aba8da53fbc18a5f568c7779521f9082e9f97ce20520621653ca44dd
-31 3 6 \\x4889f17bb59ea63aba001ac219887e45f0d70813037d23c7bb1f621b447a021356a0a5274b8153f9a691203abbff421df376055a6c59c93355836826417e2c0b 368 \\x481b30a0fb216e2a031e0c308fa74b28c11e73e6768db8faddbd4c3cd99c535703407860ebafeb720157a109a9baaa50dbcd9beacdc891eb8cc74c3ff21d326de52d82341ca4e0cd72f99a16c1a12b3ce9df048c328a2002aace3a9e695e08ecfc9df910b35b66dd6c8204c77ab6ea72197f3e686f4a6675fcac69999fb31969 \\xf063eeb4027764635c6b1c9b0a45603b9b8f73ad75c770b7989bdeb5412a91c2bba926ab7c1db8b437cab94f4442a2910bf3d4e4de3ddd62e24eaf9667ac056d \\x00000001000000012c69e4d49d1fa331bb8c1cb9f69cad2d905699968d0df6361edff2ad80e9e64fd8edcc2581f2fa844a3985e41bf3f811ffc79abdb04a307fad039fba076685e76b2b72fb43a6da14c55783956876cf5af5a9afa3dbbbaac48cab835c3d9ed1ad3ac54fcbd5aec04696c0979e5191cdd273abefd6b8b7570f7ec31a60a5f2c156
-32 3 7 \\x87c66c3c5d243b06bd481fbe376e7ea289f2e06d04b2a33796f2fd6c90cd6a6fa76b9515819e922279ec092497bbe8ac1dcd818403fce79b71a75820a49af30e 368 \\x4de3240031d802a03af0aa27304bf3b1e2461e64d8563009841c029dff459836696c21ac046acf3856b5c4857cc957711541d95c92ea1cbe6bd1dd4e6a37452d68d1ccaf33f70dd68d9f0f1de766cdf8423a4f5965fbc98c992c64b5e2ece77f2d644facbbf4f0f00c66ce082ac58acd723304733bafb42ebbc967f7da456ee3 \\x0502368db7994f04bb9dd256a94091be7965d86406e682364d38746525642ba4c62a66bc7f0376ea7365c3cef018675d7804ed848f7e2dea4e556b505c4605aa \\x000000010000000105a185181e68a198c81d6acae03d28fd37476cc74fec349e8a7faddc210b9a6b670204dd2613f8c838e5335a68cc4716086fa459f3e998b5a47fe044f10454ebc356f012d5edb832c8897dc6910f98d753e66ce6c06372c0b45ed1167565bc17f8e5976c9e1bd55249b510672182af5ee5072f419b5998a0e923dab1a177f89c
-33 3 8 \\x52837899b278a47c5002bde7bc5a25835cd6f00c688774843ebe562806eeeb117abf6bb22218075b39fe1a7c2822890ffcfdd325af4e404b837dabe868c9010d 368 \\x59a31158c66ec3c048a58f5b912f69aec7634225ca70be04f9074128d2c28cd8d7cd60eb1544e5948abfee03e0ec4d1ad13a29185f0f44bb276249129ca64d4bc4b6f944a6a08c4c26dfc425a2cd4a17f2fac1c4cc1275677813fd96b53501f0de836cb5fe64ccea2802a66b3bd418cf81e1753f2bffe5f5da206b18fc4afa32 \\x75a38c08a0d37722a2ed5cc6cbc37ead03fa13608386be053f6d0b49cfe6aa3d5ac1d1d52d2b8bd4e36a2af9c760b62dcf05be1f74542d2e30bc97dbdf29408d \\x00000001000000017ed1944eb8b503852729da97f8ed8cf510d06ebd76b9393d5c8ec641c21536fa9e95cfcb5479eda5135ff3409aaa0f125a8b978add31d934b699dcc957f8bde954b4cdd22314bd54bbec8fd106ed08472555ec49099cd0a97ac1fafe790f8f9810f4d78a451fb0cfb6428d19b3439d49cb61d47d953fb2cf4f29609375effb07
-34 3 9 \\xbad9663081a83947392112907cba16391795904951c898209f5878789c994b57ee1be4be7cf2a88729f8f48b061d302e67b1e9901e330d54dab93513b37a500e 116 \\x0b6fc9ab470a820975f818c4ca95f485afa6342a33b4be4fa9a6f42ca1cdf7cc6939b296e66b95ef9ad61f3c2ebcf4adbc532aec47fc5ea1a5aede984ca946607abb481d074e3e3defaa7526b3b2b0dd57327b1f52c94c5494bb07579f355541b44795054a656b17f5f01e3411fc3de07b9b8de18891aad91aca7f92c5b88c28 \\xbea9a3f6f81fea6d664801598d8d02f1e4ac23391fd4a8b179a8e7ac078e4eb3dd0f1f7f739fecc64492a1b9e8111d39cb203bf7fde41e8323c95f67cdbd436e \\x00000001000000016df7ff1d4868a827550dd6da36cb1f898473a8b367f31a2cc2fc12a27da52b012907e8bb0178240fbecfc7b9cff636278bce871dfea6520175afebcab0037838e1d9eba4b6390738c278dcf273e62f850d0910ee56c7ccc642b4c2c35104adeae66f376bcb8518257ea73746649f21a702181721f95883529b0e08285cbf7bfe
-35 3 10 \\x46725747e590257f3a9ab75523595af7d8406135a126f3182986e66848e4667137f88033adbba406e80fe459d2062509e4a86ced93e72566ee2dd3a4b5ef1409 116 \\x1485e75249e5eaed5b92dbfe676b2891a5486f6721ef4153e02df46e94650033a66e2e556ae2d701bca013b81a93becb0c028317d9529f6f69d317ca8d59e99f73a9971ee258bbfb4cf86d204723ab9fe313e9eb868cb7ba4b62da2b502f39a0259a7a18b70d61f084bb70b23a73c96552ba3d91fad9fa1234d7eed640f4e6b0 \\xd1ea7f176080b41683cd1e850ea43c977d2c667cedbea462c48d811d555c201f6e9f4c5ac475224bf3411789a176d6a064a09c71cd7ff47b557472643bff8463 \\x000000010000000127456570c96586ca2f731d9c6cb6fbfb4bfc50a913f7fbf2f1c9b28630ebfda20ddadc73a165f3fff89bd6a4a9d8629eba91527fcb160fbce5a115514ca5fa2021689bfc116752624e1bab0b6dedf189f8be3b56a85cadd33beae9bc5376e8815c3e6f622b440112918718d83660a28619558ac5b17129ebced61e5dd5ae86e3
-36 3 11 \\xd45f3711c80967b80220753cf2c0c824b36653c02885fa71a584b60dd95cc858260793e6fb02aa87c765e0bf46f216cbc25f448cb6e4adb845aa200adcc10a02 116 \\xa7c8c7280349a41e812e99505f7deb379214bceae532572a2fe3265556557a209638f88f7fa0dd71f1fa02bdfd962fefecef4c8ad6015597dd5a6f05a22a50d072ae9d7128d5cd40854c18291cbd8c4da8b6903e0ac450100d357e3195292287148e3ce9d28d3b8c2a7269d63c3cc3be9fb8f424da947d5c716d02de20e34038 \\x2b66e1c254edfd71f821a00ca6d7d367c4aae3d0969dcbb0c7ccb9af5a2b13df53c1e83011369f4a3717b64613851975f8a0c1f3c4b07b5287bbe9f378b6d613 \\x00000001000000015152cf44ada7d3474772d205d4dc0de35090feadabe2658c6e7549255d8c5543e2566027424fc45454a0e9df9767735116263832694ba34203a9a462437147e4804d82449ce76fbd041d574bbfd2598ca31212b43edf1e9f757443e9d35d5090125d4c940ab31ca0df8cea4fe0b03feb9901da414b03057cef56c54698663614
-37 4 0 \\x4ad307933fc055001d3183c3225b54e2c3220a2161547248b3a82876d8fa67166d625ae8da82302431c3411e3f8681e6b72efd16fe810a0e29c933e4573cc50e 256 \\xb9ce2120c1c282e85dcc286b74c7bf9658c371752147e861700786e3b16cc7b527db9a3fa7ce265363d1e3d10eab0757f8b71daebbfb62050911375f0089ec30d4e38bcad19e98738ed00df88d264ed7cd2e72a8ad41f2bf999790f5b6b048a1e3f3e9befc2f8670fd154445dd8410c43b52ab0566e78fadc1f1059ce3f3baf3 \\x18b7c7491cdcede798cf5ea10f0b7d9c1de89bb5e4f49526787cd3351e4c9a358dcb76836530887cfdcf77c28b76f536132e567a2a41833b18ff5aec4e56ce7b \\x0000000100000001229992831aa309a3c22fa278db27008d62792b59da694e36cfec61998f15cb472d55dae21b0716c3fff089315e4bbfd186e68744b4c8bb71acd1551c9d69ef578f42c7e326fe6d85dbebacacc6d3d28192f4f46279c7afec4ef870c7aece968b88980d058616d6aaf65bb8da36c58cc0d7086fe1398a439188a197e0e4d584b9
-38 4 1 \\x54c5f2743fd6620092a0f05149009ffe426fbd94e71de3357d04a4a73e5e8b5d097531ebe17f79d0f5ff73471b45f0761715724784703b86e311e60d4c16d70f 368 \\x8057b750ef210ae4719c6da535e8ce31439a613a117518031dffc47529af491c7a8ffe645b7c5bbb35112f2ada0643dfaef8a68a211e2952f7e454d783643c6c96ecc88cbf8e8411810060f1c4218c6739a80ea32bf8754ee1b1a66f7b4d43959d78d2221bfae4ff7552ffb12a1499aee5aa412344b093ecbc30f63801a38c9e \\xbb080fae4333cc1330bf84a29407f3b46b9e34600b0224c0fc4450f301ceb86ecd3e54477dd7b323baa1ba2747fc81c89dbb070f42083b1cb6d2eeda931469eb \\x000000010000000143d6586a0488740784580dd173167ce73520303ea5a073dbbbb39ab15fc0401256b3e3117bfe5d002a687d8dfdb500258fec6caeaaee8f91585e008e5acdf5c050afefab8c05025dabe981a85ab6a67e78f14acb89ccdffec5c948d9bec1b399e8f480619c1f8a085a75d1289a10f2b6054c5cb8aaecd651791e54e4ec888c59
-39 4 2 \\xfd40c6fd442a4292de28f32a9a75042c6797a2566befccf973fbf21c3c0f62585caa04b96449a9a6d863b29fb755d7a2cf6704bc44b4bd60f048e8742edc5705 368 \\x5663f04abcdca391d988c165beacbe7ea4db5e8013bce23b23bdad5870bdf209a80de70f169559d43b4df53572efc99c09a775a99842fd9acb0ad711bc850ca26d4b091662f171390e7e18124e8f85131de9005ff92335820c9ef60f625e45bc16a551ce1d9b4981b6f366bbdfa92ba1f6fce1bee69169d0990ddfaae6e8d6c6 \\x9786b16f65e87de608a6176f8393b9b341d66cac58331d03f2286de4bd707d220876118cfb2d2354962fb1f19e4a103dfb44f76563f1155bab3b2a83a61a89b8 \\x000000010000000130b44c8a72db21e962667ea4f98646c09ef7d9e01d8a40a70dfd971147ba21a35ebba41923fbdd44eb27e732937636b5270a3d9b096d4655d2229d23d8afdc807e52f00a2639d7b3ae6e5a7d65e0fcc160e760f9a245d324785cd2fec69fd77dcab1342cb334f46ee8d0f712be3b7a2f8f590d00d297a7b263e31c4160d7b921
-40 4 3 \\x415dc60fe2bc63910d390dc02760bf7f0450e2fe1a7526e52df09afc3ec67e9307208651e0f3e71f98ff144f8383be5539c63a5dc0f63ff959d6ae692709750b 368 \\xa5c88fa9b13d9821bce30de5893423b73d184c277d02446acf85a732b4a12210759dda08a2dc2af539b991e3994865b2bc918a6b1d8022004c762f55d83b632f3752aafd4519cfcf16a7c32e614ea453957fbd92d7883484924d8de1af689d541ad9f2c825d34de003b57bca26b45e503ae9d07d66fbfce26f41f15da23b69d7 \\x207839c760683ca09dd7c9f95f05940b88c04df84d47d191da18635edb849a424e2d65e33934ce908047dea79b896505221f8989d587cae4f4f7113c94bd706c \\x00000001000000012dac93a92a518ab172c1b9f00a8c1951bd65badaff07ddd13fb902e378a9a876e09fcc8f75e2cd800abfa0bc463a787f638d379cd52dd77209b3b93be6c253a64babd525b44fc663db2756f249c99cb4aace39c9972a8c9da90b303c3e449d4084835fc720f2f7d9b4b746115115f913010113b5a6d975bbf009f1c81380b4c1
-41 4 4 \\x7c445a9e30fd488b98ea7a10ed64e464e5c4f66bcf4cac8636f0c65dbd140a89d759bea2fe54c6c6ddd19454f84f9ed6bb5951adf10c4fcf96bc2752ac1e1206 368 \\x712cccda4a9d974bdd3842ad154d999fc75ac963b5554b367da0f9da3cb6bf96f8b525e9eeb953c602a6202b3a5e61a541820703aa42fa7d6319fb79159be50a1161d470c23211cb0037a00bbda728597bbe6a44a94be901e21abe1faa341307406f138d4c1c2ddecbc8de2b770f5c4c1b40466726dc9947fbad0d54d3642d0b \\x57a455af80669f5dc7dd12087e983424bb64e55d9bd2ad41f53845539e9d8e59c8fa34fea69091d55b3aef4ae0b3921e1f4b576d4373f4a3a25c0d72a3dd33f3 \\x0000000100000001ac5b1602977f1ab84cb34d19c1ecab89af9531920de56fefc0b650fe636e669913634842c1f4d4ff573d980ed28787fb11d5c2ff62c1391c02f5661726feb7f8321300406669c5c5defa2272e413ca5f71287ebed5b625c8dd5f94d4bcd4a0e3a8ae95789c807f4121707cb2a79ca625f07a7071cf3725ae7d3834fd2fb6ecbd
-42 4 5 \\x73341c0d1541e833c933a368d13218704e259363f28487041ab467d293d3c251d70c88deb9b811f20ebe6068d9c760d40239ff9ed58620e6a48831c7ea51020b 368 \\x632670130851a27688eda0f3513c42cf548a6ee6b790c7238da4cecbe613930875393479dde805df19569b33f3b5311620941dd01fc96ae173bab2a4c458555849633c6ea73133ecdc8f655a2ffb584ba974d516bbea92579397b8fbc06b3a6d528e852ec117600ab796f6fd53c623ec52a2a786382263a74650372f116d9ee9 \\x70e6254cc347501d1e5d249b2d17bdc3aabede74895226a251d3502219c8e18cedb24a54a9b601ff5de3fa9112efc35214e1864d968b98937756ca0d1ae317d5 \\x00000001000000011eca3d5159ba9297c7a10070679fb2fbfd9e7bdfe2e3e8319564167a0c5dcd57e9b8f387499097f87c808dc3d7766337b162ed42f4b2d5fc739421f2ace3d1d4d4ce0e2e76f5c973417c9e1dd92f56147862978667315eed7109724dcdc1939c197060cb31c33cc4b6dd21b553aa03f44e5c05d8025bc00315c937a352a1700e
-43 4 6 \\x4a647e759bdffa43149fbab112e70dee5f7f42270e64c217faa2bc0c10429b3e5d34c967d1f60e71a0e7971f6fdc910adcfcb6cf68564adb0824c8ca8030af03 368 \\xc8e5b06c9a3d60c936efa3d1f1757326fe5fac0ad04cbf1a9e8d499ecec9e6eb073bcd5e0b2c7a46dd477fb68d1a2d523fb898a7986e3411d4c5f72d18bb8e2e04a39ec0d5810134279badbd07646e417846714fc4306dc85b951f2b45d2787c8ab9f82de5bbadeff5a886509cf6d80d31ceef883c40c69b097539e5fffd927d \\x0df319b51cc19742b218f869540aebb662e47c88ce17606892c3f1365292292a7e7876632fefe964f1fedeb5a88ecfd2383069952521e2742083884960185b91 \\x000000010000000124d6885cb724790600003ca90424b3233ef6966c811c618ece3f452f40163312ea8f47895e59e4a5a8595304910625c5f61cbb702ac1a9544ebe706d472b4b65eca90010024052237fe22f187d1508ca3a23e78ca01902d0281d5d7a7d6c38b8c1ffd687d23d91bf3026e84c79e8c641c9a24abd7a2d9a0498c5a90fa9c7cc00
-44 4 7 \\x5b5dc2887e1df7c2ce2d9c12b18016cee0d6ee1e4d10b4c8f411e15c3d1ce5ec1ce8bfd060284fbc1015d0c591806ebbdf7e1cf40e4d9b8d9f2387e7cbac5e07 368 \\xa82d3a5310a55d77e59a74bbd625bedaf6d1594a3b4ce40b1136d0375f4e49639533b1639ec016cd6967386acc6b0ce57c65fc7aaa45adb2547ee6217178f1993ea4298553f4f0fdc32546411faa1c666fec660c72235e045c213d189323b18e00f27a44cbe8b5dcc476b4707fce58a937d1d0e49dd58c451603dbf2b6bc082f \\xd160fad51615e4240680efdb092e8f727737b918883a18cb543147a56521260fd933fa85312fa32e570158e930674e1e56ad30e1a8d37a5a57208fd58d851fd5 \\x00000001000000013f0cceb3df7a7e5ce48fce92f0e087a2437cd2688df819b96580fea9e43b1904c85338694ef1bb0c03ad8b3a93476989c8da10e71948c7abe9b54f7e42eab82859cbefcb5a4558d95a9ceff382eff159e7200ffb4d2d21d73e7ef464c532797c3490cc551deae3a6ca626a606d1669defe25323cc251a090b3ea4ddbee5f2e70
-45 4 8 \\x54a5dcfc6fc9427df4c29e29bb5db53351f57de5d44386470a7e88a6d25c1512b99ecbde1fce80d639d6aae417f91ae19d21b0e5f9b46c0af2ccdf0a12c37f0c 368 \\x27de0f254c2fdcc94723f3bfedfa21be1e9fda6a0b2b1c256f6b1e2dcd5c17fb27c33d913f377a837adc9c7446084a48fafc496dcca06c9d6f6df60df3ecf5e9d03bfce6884517257699a071dc6af59bb24bf1f91c4d65d9b18a3c1b9078d49491915abb72814de328083458a232bd0fbca5112c5adbfd6cc9248404913aba6d \\x92c0b7d9cfed74ea115b1748c301a7226ca2f22f95783d58b49ebd4422b0161381a67bfdf4e188f7ce167b58fff419b76a93bf447e8df056fa6956af7b8bc330 \\x00000001000000018ba36c89ed7643772b945817dbcc903197eb787588708a0e482bb4a070aea093ae6ce85eaf3e9e7a0907aef2670492e29025d9fc5dd75496a4f1076807aa81ab771809a1f749b566b2a790c571d1970567f551e08e7717ef9cea78880d580b13878177f6ac163a4b66e7487fc0511bf329a3b7d7e2cfc4ce2d1c86266b353f10
-46 4 9 \\xfcf71eab6d90d9970c3a5191439a7468ef52088442abf1c7e7424d048fe191de8ce7c744df2bca1796e5a08e85e60c536f7ee266f2fb8b42cb3fc92033c63b02 116 \\x22c2bd48c64e5984ccc8de64aa2dfaaacb9ff8ffc821b3a31cc3f2eccebd9f0b61dc5061ff65cadfdc5bcc7464806cfc6dde46818d2577284c2230756458d212eae4b9512cecaeeed5adeba09e63d74d87adcc42471d9a213bbc1b4ffe9f0db3a018b629986e7ca1cd79ceabd6fe00a81976900d5bc3324d2865d1b35e74087e \\x564f6710643d6f310616ae3458b2374750e679ba625c4a89c18bcfdb629ce7059415b41e7c34b91040fc5f97e845e9975f6ecb0963622b323eef8a537a1df843 \\x00000001000000013a6bdc98da59bb8e804a5f2d85afb9ca887ddc4502671c6ca3d00fa41779daf7f361f088af0ef55c8ec5daa11d461a02c815413cd8585d034deca820492fc64352886c4afbac77fe05d850b44df4da78cf4cedc060e57a4d06d3a5f7bca1b39ba8775c7eae140cc2dc0d6dcd35c63464c48ebfc5ddd817fd9c4b9f79a5c3d660
-47 4 10 \\xa3191d6aefc19052c80ff793da212dd50e5ba36196e5790639e56e94787bac78c740e0039c94ca256f2e7ef2553be06e58ce00e2871d54adb0690728ddcbc70d 116 \\xade610a4153524d17680849e9be079ec254927ada0f32f126c33deef246b0fe7d580aaa84730c195c59481dcbce94ef23065a044c38275710a8d54b275ce8b829ed0cf92909d3710ed71dd93acafe7d905ee992c1b3104ef4f9f56a186fb2320e71b23ca9daddee89dd7bae79b1d5b7f66e909677bf4ea589b9cb18f8fe79c4e \\xf963e76f485176f2ad40e580340c62a644ed7254c4beca681901d07f78b24d86c877214a781b0acf68be867d09988f88b486bd94954b8c47d69a759dda6a72b6 \\x00000001000000011677f6f0ed66fdb0143d3337fd7231e7d4c06e6c084aa7f56dcce39c17a1d9ad2a94e8c5f2498ef81720b1313e44127e25cf15c272c366f8fd8b05f661066032614bb7c42afe425662226f11fdff6b13678341e85f8b47ab2678c0d896f3c0dc7655bb0e56da7f19290f835a458559a8cff6d8d5923103dd796db9a9af8e3b98
-48 4 11 \\xadf8459054deae049c12be1d85aed596418b6742bcdcb161744bcd52be5b2f41fbe098ab3384e5ab0902a12589f9a2fea5bfad621c3b71182633ca366d135308 116 \\x19d390e61cb1c44e141f293a502b902b54b8faab793c93d700b46b360917979ffa9db71452a5ac5b5077ccb8c72165f901355a431ee9d0c48d5b82c875e183a8e0778d4e9443110a8be0a6faf7d869647e2ddd4fbc3478de0d890db98350adcb01c7f1b78ebcf54d92e9405804bf8612c1037213686d319c4c7d57c93bd02ce7 \\x1c7e0165ebc2d8998b857ef69e103ef9090d640a9d80bf0012a7acda9280c24d6fdfd0ba6b761c12bab652073b21578788a93dedc9ca968345a510cb11055d49 \\x00000001000000010e0c5761213737846f3cb7e325294db727e0c55aa1748e4d468f203f5c68856a10824c172070c61eb1d0fbc2b1f9ca4bbce0dc91e2053c2a411fbcecbd7e02ddeda8b880da581cea4e06f3107cb28a2389c67e0422afffdf0c943773984c967976f0624840ff501d134346853ef47f9dab35b05bd0b9a883006d49727817e54a
-\.
-
-
---
--- Data for Name: refresh_transfer_keys; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refresh_transfer_keys (rtc_serial, melt_serial_id, transfer_pub, transfer_privs) FROM stdin;
-1 1 \\xa469a8bf92b19151212f4910b0276bb45e4b0c53d19105c7b7fc6e3609278d5b \\xef2b081d2af6c0191bd00320594842f9f00328612cb02bccc814967263be67a0e1903934ea514eb87a62a7d794ee6c83efdfb372841b3ceefc87f4a68362b8f3
-2 2 \\xa2c6e3d1b1f77ec1a7b65b2d0c64e683ac314162616d5cb3c3ed0d90617cde2e \\x6c309237aadd40da73d53bb2cef016e71dd9f958db29db3eb5152a55e3c13dc4a703f52a7b17bfe12e8eb29c8fed9bf0b9a8c3e5e0a95a6de08613ae827e0e68
-3 3 \\x5e4284a97f5a2bce08e3cc642608eaf631b4159b817a689f5f701bc145c3ca63 \\xe1d5ac953c93d658102deb1e6d9899a1b84b6d5e3f85d0b53df330dfcc9279f522256282a50193f44119eb7ca8db107de84a97e6ea60617ed5e97c934680fd86
-4 4 \\xb40e94ca1b823496b3124e6efbd3756395ee75c6254286a12e430f82f694bd0f \\xb740d841faacc3bde747769d1c018b1e3c0c1da81f7c4d0d8033fde5e402346fe7f6b1344eca87b5239712025ccdc8fa0d9f8865feecc630f5b775e8c3f7d9dc
-\.
-
-
---
--- Data for Name: refunds; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refunds (refund_serial_id, deposit_serial_id, merchant_sig, rtransaction_id, amount_with_fee_val, amount_with_fee_frac) FROM stdin;
-1 2 \\xd3d7e30b2407d1065669bff040e485fde1588b0f1e77a9b7024ed39222c4c0c00a7239f461114d91ff8a6e1a819a93e15c38de518e8a74b295a0e85ebfdd680a 1 6 0
-\.
-
-
---
--- Data for Name: reserves; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.reserves (reserve_uuid, reserve_pub, current_balance_val, current_balance_frac, expiration_date, gc_date) FROM stdin;
-1 \\x4265b55ed43900b7b0f72d6c35c1530c92acaa04632ced0472d67363cba95226 0 1000000 1640776121000000 1859108925000000
-2 \\x00355cdf2246de2b154c861fb2ea1ee2e25c9ca526825db13d073f1de8131562 0 1000000 1640776130000000 1859108932000000
-\.
-
-
---
--- Data for Name: reserves_close; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.reserves_close (close_uuid, reserve_uuid, execution_date, wtid, wire_target_serial_id, 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_uuid, wire_reference, credit_val, credit_frac, wire_source_serial_id, exchange_account_section, execution_date) FROM stdin;
-1 1 2 10 0 1 exchange-account-1 1638356921000000
-2 2 4 18 0 3 exchange-account-1 1638356930000000
-\.
-
-
---
--- Data for Name: reserves_out; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.reserves_out (reserve_out_serial_id, h_blind_ev, denominations_serial, denom_sig, reserve_uuid, reserve_sig, execution_date, amount_with_fee_val, amount_with_fee_frac) FROM stdin;
-1 \\x69df10b3fbae5dde7f5ba6cdf2db1288323ed99df825c54e2b00ab905eb8c71560a1478b06d466eeb09c977b5f563fff59b2159dcda8f2fad974f72c122e9533 224 \\x000000010000000141bd7c40f0b3b6990b2b8eca7133971e139b60789826d8f4d5c1f6070477d1b9202e3acedbb9afdc72b0368a64293b539b1668cf42fa45bdaff7495388d4bae79e519e594f0d20b67e9bac3f209caefab8f2baeae8af3c3f0fdd4f401fb6c6388ea5a0dfbaa821dd05faf1f9a0e93935cb4299a2d027ed42eb80f2615ce90b92 1 \\x254c5200300321296b49d32c556d2f459cdbd5276e7a65b8f63633ca4c16f8d5d82a02ef7e3b16cdd449a7677f6afe6c1a47c57fb97d07e053c2e0dfc5b08703 1638356924000000 8 5000000
-2 \\xd45febfdaa13c775b9923d0c8afdf6173526aebff86b654b2578140914c81971f316497437638f96bb7a5e8aebd0c4b2f7ab07a4ebb01e5b975186897fb11ee5 256 \\x000000010000000122f6abb75300c79ca54084de5a72dd4d2aee6e24c1cdeb6cc7e5bcb9455dbf9d4cb35319bdd4493fc706e445d8111f3c5de8cc2d17c0c62b5e7348f17b36870b52fd2492e352e5a2287019f3309095da2682e6b0feee9227a6afba0d58ba85c2833dfe2d58db7aeaf7f7f305d0dca30932ec02fae7837370cc6e6226b8b06842 1 \\xa92902d2e611cba5897553a4c3f6ad0bffc67f4886ae4bd12e1513ee25c2f7cdd5bbaa814f8d4ce4f10ce10c0e3db2f697a4d3ba3f54c55af1ab0000f62f6900 1638356924000000 1 2000000
-3 \\xbca25d1901d4288db08a6a90330669b34895ffc5b5ec258def040d49ba5a674f56564e593a6c7161c1ea6d052fa3a538f02cefa56be20b43034b269f7b029a0e 368 \\x0000000100000001bad811a8e6984128b0fa76cbac00d3956c654ce8e7065cd797e529570b848a7a68ad51d27f65d853a300c22e4c7c4a790afe57216d2e542a7a17ad6693c9603e989dad891cba4765c24d5f45d100b419baeeed03c1db67438b2afe203506b18c090b61cb7eac0a1ebd5e82af32e08145f58852fa63ca9995963d8006cb83a8bf 1 \\x72c1ed535fd33e91f8bbe9b70eec93f8a58cb0f64604346842bb57331f7700286d59e0577ed66fae37ada79df35d631750a193b396e4996e091587c1a28aca0e 1638356924000000 0 11000000
-4 \\x1d3c1382ef4be136aeea371531ef3609933a59628a96b5aab5898551d7e90e150bf430dbef6547f34b6fe9c43cc8613f36765f08ada3eda2e42a2e32e796ed5e 368 \\x000000010000000162b76b36b691741450089999b7b1d26f4a1f824282e86628537ac60e99ac5a929660399f67758b10bbb17e35fec22bb68b482a4193795409d4406348fbf621e457f1d830a51b00373db9ebe72b7450cf1340e2ab87cb82c745972cf48083f84bbb0f8b980b613b5aa7b1824dd9d5b2b1ff970574f6201a891059c29ad37330ee 1 \\x6772024dee19ed9af82cafc9b27c76508891c599e76e73e04c1235c9731346d31838d6db8cef1d3dca49ffe7b142d4e7f6e65ec13d516a76e79965038eb79d09 1638356924000000 0 11000000
-5 \\x695af2745ec91d7dce905b48bb9b3a39a85309b2663b122c1f51f3b9c7a3f4a1ac44037aa417de8dad2cccfdc2d56dfb84d365ab4e298a77c2a3db6ee2adf7d7 368 \\x0000000100000001be4e6d48d8b07b290e9a6c268c4047ff333b3cb17b93777a96f2c56d862b79fc5552e6156b6ab519a5ff8fd575b34d142b26599cc50a48893b12b5fcd2e9e20e764b2318705c0d224bc93896609e9f28755ff5f6ec7e6156eaf8a240dda9a945b904626b115137ce4b8719f61f87031eba863e5063162747c96e4113b15429e1 1 \\xb18b9f3c3933ce569713e5a81748aff4c7abc549b61f010e9ff11dae3e1413961072914d8bfc3972dacbd8d95ce0d69b2207acca7a5816abcc861171067abf06 1638356925000000 0 11000000
-6 \\x9836b9cda16d9825b583ab42c1b5e57292d4b7af7221e6a6536c23b9cdcb5f3fa6e33896c1e2ece49c94c46d3192d6e0ff3d73f5969be49ad736f06106fd2fed 368 \\x00000001000000018afee3ec560103cc124a0dd95cb5916a7a5d93eb465f4b037dae01484a1a17d271fa0887c3eb53f536e2cf8fb41a7493184c0b76fc879788a3785a75ab2d80a23b93de65cffabfb4c3cd1506c927e807feaef147c73c473e27385030dca02daedeccdbb55f74718d944e3933b2bcb482f1f3afb6f4348ac6c2d4f4655c18a25f 1 \\x20f843c20b3262a86d0cc509acf19f2aa2e3f7937b6e82dded8bf7fe847b46a3a9ff2bc03a30a548afe16a342a0e68506adfa7b93bdacfded6226637423f150f 1638356925000000 0 11000000
-7 \\x2e90fd1ce7c0e3207eb966614c3936a76fe14e316ab8763acef90d9ba07f3a9ec3bd8bc0b1b2de3bf5aae6df4d524fee53ae6ac526378f354671b353ca4b52c1 368 \\x0000000100000001a247a5ee312d83347d8376960bb7da929047e5f21f4655a8b2bc78adeb7f13c39b3feb5a7471201fc4d65f69e7433f1a84bf0e15e68a0f83d8a2523e05bebd1083726b57529caff847c4d8529ad01ad23772c195b20321f0363ebe50a1850225fc3756713c50ec4d3ced127ffe5205d139688605afe54f675d3e40f7c247a8c1 1 \\xe33b771581d61cac9d3164f4ddca8d51839795fd1fc43b60bab8be1e1a71b95fcc913979216b720f139b884a9ff98be236c8533bf3665c32f0a92ccc7320a505 1638356925000000 0 11000000
-8 \\x1827265513aa2f495b86bede0d5096d192b19f2e4a3da4dc78e563acb9f7967af6ca898122fc50acad89c81f1a204b72bf0d24519108a7af3416688215189a7b 368 \\x00000001000000010d08ea73b961d6224aff86bb9cc9626603bd28726e501eaabb61830c38d5a89060770535bbaf0eaead6c57d0d51b3f394e508a68de2c87e6782dff23b60e509284c308593eefc485eb9eb7dc8785490b9f4330d03a8817edc620ca53d6b8e5ea26f15ef4652b7bd1aea6e3c9d193347f45f6bedf3ee17670da0d8c1bbed892f9 1 \\x5caff38c0a0e6edd87be5c36d8805fb49b4c553db045846d56d76457ffae7c8920099eb5c8ed917d0eac191ec0b69b44369e138f007c9d923c7e765150632d06 1638356925000000 0 11000000
-9 \\x2e5d03bf792e4da8484d4e777d7fd991d085258658f4db4d090ccd7723d78ddc7fe56c051b3fb01b8494917d8af1c48476c913ea57acedf06903c81c1b777eaf 368 \\x0000000100000001af0e02649bcb0211e4ebba008c934ef967254027e6e8f7e4bed956b8c255785e341623164d2353161643e5b1d1db39ebdb848fafd5d02f5d52106d0512fc17b5014eef2bf836795a52660a227e0e0be439234058be34db1e7495a49370426140efae8eb95dfe00164d8c1469bcfe1c883ed3244024a43d38df94fef45ae5c306 1 \\x37ece730f90ce3ba43d56f72b21d9d9bed62a4c0603b91fcb590fd90dd60c493d48a8a5cfbbd7895c4eba731fdc5e1c93f3b44ee5f06c858d7c9eee89da02e08 1638356925000000 0 11000000
-10 \\xf653bccd0da11484bbf0d69e6156f6750939ac9c1d89f74f802eba3d3dd15e70454afbd35c256309cd9f7673328badbe8e6b530bf5c18b3153a18166d5d0a920 368 \\x00000001000000012ce7291e13b9a8639b5eca99d88877b162fbece208a2f0455668a0b05c1c011645c513039a93fa1551a597a92495125fcefc1de1a8ee3ca459806cfb35170d3ab07cbf518ca103f5dcb679cdc5ebbbcf35043dd5c082a955590b7c3c5e939fe9b5f8896e3bede2f98a30cbcde5f30c95d9cc66888fb59f01aee287c433c8f7dc 1 \\xcf690774a85640d314c6bd3d323cbba84a2d15cb2ea144eb2301bc73815546a2bf63521bb347bee40777d1b03896cafaa6369057f1fa26d72a3828d529b0e902 1638356925000000 0 11000000
-11 \\xfb055395da086d702b40e674e38e959ec6bce7476fc62c766662e6761b9132c67d94072dbf4404ec024ac99159b4c93bfc6291c1369849ebb966f9de570c16b8 116 \\x00000001000000016f1bdedde7d94692b2e7c13c14545489f178d263f9212ec5a5b90562dcb3111dbb8098137de41d5f561efcb5c6c22b7f9fb00eb1f24bcca7239d1082c91a22e1c76fb46e5d978e30d2221b895cef0a11332d6aec85a5bfab31ad1fae6b9ee31a6ce548c1e19cdc732e9081db0c6e967430ee462f40cd8e5b4ff3b5875dfe1cb5 1 \\x130f0b68807f8d686ab4be776ed8ea81380bc384b25e96b2d9e62faf0a8615261364634c7c201c0229cc8f07c153a416036bdcbe523902c327d15118aa330d0e 1638356925000000 0 2000000
-12 \\x7239cff5a5d475359aa8965d8cfd24dbbf4b1c8cbd717082d39c21d377abacfb2debca43224d683ad5da9ab1fb3a027812b74138455d8ce54a554f2684ca43c8 116 \\x000000010000000173ce053f4ae96dcecf0a63f0eb4451a836e1044d583b5e582e0a1cf63ba2dd3381c77a8d5d12a086595876586e211042e32f6e63a520b03df59cf93f38f80e3a1afbed2d53dc08c5ba8939211af384489c64cf2d4c5c8a648227653048fc297be1c9c005c72c33d666594a26f1f75cd7e81c2135e3a13fe1bccf8e6a7512bdc9 1 \\xda606ff1500f20ef95f32bf0395d8b2799c5e13f5baf43d6f6ad89ab8a28215883164d6f9f2b3dc1ab8c099074291b006b681a67705b88160cee18f6d0e0e60c 1638356925000000 0 2000000
-13 \\x219a5bb73a115ef8502f20d6017ea7d6cee5d07344612f4f5b7007e3bd17e7e53d19e1b49efcad245dc2693cb5fd92aa5e1bd844a560e62f340eba57a2457219 155 \\x0000000100000001290e9b8671699f02df36e458cd29633193bc52a5b00f8b2263b1975ae03828c347a5761280d200bdf52f5bb58e9c5f82c1c39e6e43f52cee5673e5be0f7fabee6ffec0d13e5962487573809fbcdd8c5f521b7b9bdc30ab39cf2627b128a1a1b1adfbe596cf7a9d63dc5040cd97bb84f5176df3438ebf37aa27c8649050d9fc88 2 \\xbc7ecc7e1faa3270f6974991b8425b11ab09038617736ca6d153f5abc37745fc5e24c5bef0308bfe43c6ee95aa9e640b074d0124a3311ad009f299a5e30ff701 1638356932000000 10 1000000
-14 \\x63886e618dee31bc7d16b587f05509fa392370ef21936644e33b003f01bfd8289e4944216a304acdd45fbc042ba6a261dbcf7e3c8cb9f50b9e9e3cc1d18bfe8b 158 \\x000000010000000107e643225430e51656297b8e37727574e3080852576dd4e35c7c599470261a7b5e83d22b9b4b2508a6dfa69d9e975cf52839caf6e053e33bf050a9ee88273ffcf7d339ca4b3ed247456c52e9462c06c61f919bc97b4727a7be99b2ad36f617383c75d9b4794d2d44632db26788ee9267ab0b5d65513525b133ece29bcf804671 2 \\x791c46fa51d9c4c9c0f0b59102ba77c09cf2fc766e4b32c3cd0767368e9547d27a0c7efd294f5c775f7422e816bf1155a878c7b8c6a56269a0ad48070ff94e01 1638356932000000 5 1000000
-15 \\x986dec0e0deae1716acb9102d689886db5cb578391c447ad4acc126d98b3392576b477dd5079d2da81497bef6cda2e878922be10eb011ac771d101112383917c 335 \\x00000001000000013a5b5a1568ea43575837257ad546ac74e31d0e1b7ef9faab4919a36f06feac8d837c64c14449e882d8911090aa31bbf50eacada615595622e7c63dceac8001c539a7f42d0666be8dfdcfad39fda7b362cdab15b7fa18ef79aff7e56a6075eccbe2901aaff23a5a5ec9e21968854a95ad5ced84358770723c7aa6f2c718782e69 2 \\xee1f3a17f8fd9ac66e58efc4ce688b70306e5acfa7c7b4937d1b8693cd11feba2019d1ab82c7c900f3e9c49a1a22bd0da3f0103cea26decf18f181b65e365f0a 1638356932000000 2 3000000
-16 \\x0d2e8967a73606b2fd7ff685a5b99c23b2eea6c91377f558565af8699c80acb635be64fcdcc783920669d932339492c0a6249520118b2c5927bdd4d388e7a391 368 \\x00000001000000017f1287445bce80685ed4aa5bdf8ecc7591a1322c31438557666d81e3ae714ec59159525c574671f11f4648da66b60bc1457c48ecda546f5b1b71f203add5afdcc22dfd94ad42fac38494a812c6232ef9a62ca22c4c81e91faf408860c6df1869a5efc3ec289550c5623cf31570f92ea5ecf0dd958f7f3d9ab477ff479f35b1e0 2 \\x566c21fb9948f8f58cb5f0598f1777a8d215ad376083c8d347d2bafa78b8283694072785e6045eb38bc378fd9b29e6720a5581a50f8f4a634e5e2eb76b15fd08 1638356932000000 0 11000000
-17 \\x107759c8b45649dd746a7fe138ffad96b747839cc4f5ea05722308ed29e04ae84f4d0e51b7e2cb14db50405b0dd8e04036e691b32ed3e0f68ad2d00d5e5c62ff 368 \\x000000010000000102acb2c0148a5bf07fa8bccb90020369c5527e586be4daff458d641db4e9930a5fd5dc7cd9ee6ebf92c79d119e3a6ece48a95443d2edd83c3517305c5edd1d930623c6986412ac8be0352bceac279dcd4266f0928cabece3c2c0b71d0b60c5fe7334dd08749cad2e5653ddf3150e08ed2d7b0dfbc27ee6f0867bd5d68ec6ae93 2 \\x5df83d2faf76305436f2333069fa58591461c1f7db3df37e93b178af4e14f36162560b18311369b5a2ee028aae6d06aa6a9a9bb00e0149df08854a31c8fbfe00 1638356932000000 0 11000000
-18 \\x2d83f40f65782dd39926f3bbd1bd41c0cb8b12d52891d0dec23f0e27d040c8f01ec41d1a0ee06175067729b6f51c5af8b8176853c2ad764049cd7cb617409261 368 \\x0000000100000001afb81b4ddff1ca98136ca2d9f26876d8ee52479cf93b5d1bbe3cad6e48796265f356ac3796248205eea75940f8e2cc2e2acd7457c62f69d7427379abf7bb0d6d05efa85fe85dcdde0fdce44acbf7e91162e37459688de1380a40d8110dccccbf167dc5b1104dca8d346c62656295fb8c2c9e7edf0e4c9c5949feb3e66e82b2d0 2 \\xb31b5dd6ad9eba54798a09c5f489fac61fd66619ff3bbfb18a6299664de13c40994aff016b062886026314d9c6aee3e51b5baf7125893513e90f3b97d39b5a0f 1638356932000000 0 11000000
-19 \\x5f3f70f3bc10fa0d39e3a258f469b23625dbc149b1fed3af5090a79ff019ae738a4c1d38165639233b691c5ffdc8c6c3d0ed84951afa284ea223488db0eee4a7 368 \\x0000000100000001027fa9b1e80384500a7cd7d7f5e411b6ac525484192ea1c7ffc5967688d6f855b88e474af767a40e9ea6e7707130afbcb8a049a96934bd27f5308152154b5db09330b84dd4dc792a67fe5a6c8d32f18537870870e4290857f543810a9738d4b434fb33470c751faff39a9f6840ead346e3b8bc47d1c5c2552acb341218203f22 2 \\xfa205e85ee345ab75697fd77516b6c57b52ac21561a9efc1b2e971b9e163449cb21defd4b12c217794452a2c5f7d8378f40e3ff660fd536246d3e4caec914509 1638356932000000 0 11000000
-20 \\xee4d1453919b60c92c099958241c723b307afedd489dffff5df8f552d55e3a453120a1bb8d29866eb58cfe8423518b6fc5fe9ec014cb129e2068616f21d76977 368 \\x00000001000000010536dbc54fb685c2418758646016e8fded0d9ea120cdeee0e10b8c86f274c703412d863cf57a3408cf148cd6aa6b1bfade7e4f54757064e965a7dd5c8f262b880d91d3629046d15ef8bb4c1c75d2e3b48e82a7d1f221659bf262e58c41be54cf877b069a473a27e12798af714240df39aa044ad2dc6c41b053f86e183c42227d 2 \\x695b908bfc3d33ab51a55ea747fef4e4a013226372229d77b37ac69d68cb234d9a809c642a270fe5db5ad44bc8efced322a8dbbc45df49c093130b5542d8ca06 1638356932000000 0 11000000
-21 \\xb98181f7621d3cab43d42832d8812bd41fe209dd0a5d6da958a2a912beec1415dd65602f10940d0649c3a82dca6597923375f35122281fbd3c74ec3e6c19da7d 368 \\x00000001000000013a39925af579fb22ace80950827fc72bd1e3b3003da2e848ea6d0fe6532daada78ec320d57ab90d207b4e12834582f6a869c636276f2581f9924c95abde58980cca8a8c5668be8a6020ca8aa5f7e155574209de499d8b6d179458db3a5c5b410f4f24728507e798883d2e7c5ff740e3dc095951cc18e5e46bcc62eb5bff771fc 2 \\x13d916a5bca336b55b4fd0e13f6cc408b0242dba23f6974c814678c4bec7d5d61070599720d1405dca6abb9899f7faf9e2d02fb2e14c47f215a55d2f039f1407 1638356932000000 0 11000000
-22 \\xc68ed999367719d3eda40c4d544e7e23451982e093f40bdeca2f6cafac47a3dc2688c5a29833ae8971fc31aa988d2216c6e35ebc97acde086adceba63c5b7512 368 \\x00000001000000011ee10deea8a22bcd428d7e1688d35d70caffa2c84918f335541eba8d2b7a3be33aa8517ada86ba9c2e35cf0ed6b06a091e2b9f07afb9c40844b21f25a2d90e9de9c9cb29ca82f7eb8e843a4a8352b7939332a8a9ddf232ab34c4da013f7e392e7ee8d78989b146c7c74b81470ca1166ec0dd2aac32548fb7130024514de41cbb 2 \\xb722918f5050b9d82fa21aee8406bb0f209ad55f1d1eb3023f7e59eec5d6027a099fd58345c501c1b823ae6eb71b8f9c7ff50882c9d313c42239093b8d8d4a03 1638356932000000 0 11000000
-23 \\x34cef93565c9e898b3397322b2650812b27d5224fb04332c88cc62f1ef8f13540d314986c3916ae722f301c2eb13391c06ab8356439fc2976bb003c441555292 368 \\x0000000100000001914c931b2cfc059bffd4c5d1f966b2c9baf3d812c30234c030e98e582715de7f297e7ad3d6b266d6d7b68a8b9c55459ce4eb59b8b10c6b3fc139177d23722e0a3abd4d37cd17d726996b20e651166f786a41775aa496fdba41a9c054984196996034b4a7a50318c16644240f2cff642d60f7d589208c619e15bb6b2cde6bd2af 2 \\x219678ca84967a3dd67e297a354c02f32aebf99b785336f8319ee4e52fa8e65259cc38c1c4afb1f1bdcf0546a67afd0b1d9b7f5d5ea62a6316ed1844bc91c909 1638356932000000 0 11000000
-24 \\x3a7908cb58efaf39ecd45e74fc73b0aa5b32a16462859876b46d111e92a04aaeba38baf5924ceb3601cc147801ef805b031460c0dac510215c409adf9fef065f 116 \\x00000001000000011dd701e9efb125c39da2ccb5186f416f37f32cf82cd70dd8a021fc1ca92f1e936884455df8d7033800759fb2d3ee680995bb8069dafc4f882dbab6fddf8caca4962e83a4e5b4184f03c7985e9e9eb7f9c1e0bcbf3011ec2e4d32b15123e76c2cdff1e20a3e1b72ee97c6d9cf7b8cd72504241713c0b2fbe9dbbdf6654619ce1d 2 \\x5dca4c16463ed30071096d5238540b4ffaf13aa9004eea6339d0b9a3b57461863130ef8d220172cc519e9c9f58776b39a089a35b6cbda36cfd089c3a8b9a8906 1638356932000000 0 2000000
-25 \\x37f65fbcf93b376cd5a68644f9e4ad632d1acd73c0bdb0008f95ca28756291a9d83e571c798a69534976c1b5c3d7afd8c57716d5acc433595cad63e9d48d4f45 116 \\x00000001000000014b35f5faaf4581ca37f401031ac151e6f7b6b8957125703f161fcda10c02c524b539fcdeb476e207058223c29244c09cadac5a73cd0bf82ce8d5320ed4ef9221b9cb5a3c220b46675693d9f332f4ce8bd09693be3db8b2cd20afec0d6b34fc6615202cede3099742c1bf0f1a00fb35ba2831a0cb2602429f03f9f5f5ecd74cd7 2 \\xbc73f16cac7dd63e68cc055c61c85ced9a09b3fe3d2ba6657fcbd037d3b54ae13dce20edede80897d6696e8926db8bf8b58a374d093a78ad2496c40265ab410a 1638356932000000 0 2000000
-26 \\xed5536533cae2500503b66ec52f5b018d5d2e46a261ec6f16ecb64b463171f64396814518b0f1751210689e2cc37793feac66d9b4bbdcebf5bd6e9b6425a1ac4 116 \\x00000001000000018d70325dcf083247d3854fa81b57a1b8bd96bc1e1edaefc5965d01f74c82d2f97117083e557f68eeb01d7f65318dd6cca5bd11e0073229bfb0b8abc99cf4f70944ba6236f3c7a614f6ace9ed9089ea18227bf36b62df21da0b864157aa659048232f6e6906bffb5fbc3a21a5b632d936fae7f1b14e0bc4fa5da65756ab67a540 2 \\xf06b49047026e1f97fcea97eb2c19555e0323991621a79b8932a666251083405c0e57d1159bfd17c8c48543b9c8a9dbac15b6e1e3d75102e5523861697f76b03 1638356932000000 0 2000000
-\.
-
-
---
--- Data for Name: revolving_work_shards; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.revolving_work_shards (shard_serial_id, last_attempt, start_row, end_row, active, job_name) FROM stdin;
-\.
-
-
---
--- Data for Name: signkey_revocations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.signkey_revocations (signkey_revocations_serial_id, esk_serial, master_sig) FROM stdin;
-\.
-
-
---
--- Data for Name: wire_accounts; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.wire_accounts (payto_uri, master_sig, is_active, last_change) FROM stdin;
-payto://x-taler-bank/localhost/Exchange \\x0943be92e9d54af5ea96c415b17d5884a429de4c8891bbf908e857023d67a3820b8a5cad2886ddf62710ff84e8b695b45644d51d80403120ac2f3f36b836950b t 1638356914000000
-\.
-
-
---
--- 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_fee_serial, wire_method, start_date, end_date, wire_fee_val, wire_fee_frac, closing_fee_val, closing_fee_frac, master_sig) FROM stdin;
-1 x-taler-bank 1609459200000000 1640995200000000 0 1000000 0 1000000 \\x0d82feaa94d63ddd13346840135e4a74893bb825ac9838a99a3861024eef918e5af74401e3cddc7c8202d6f84be76f5256454d4af258d1bd9978d3a9499f1501
-\.
-
-
---
--- Data for Name: wire_out; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.wire_out (wireout_uuid, execution_date, wtid_raw, wire_target_serial_id, exchange_account_section, amount_val, amount_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: wire_targets; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.wire_targets (wire_target_serial_id, h_payto, payto_uri, kyc_ok, external_id) FROM stdin;
-1 \\xbd616f2bb42e5615ef0271a80598b6522cfe417d62b39989331491ad0d17af19044a61ed4a28a3b6eecbdfbcb1be10809f24d4978fda96710c4471e499a665ea payto://x-taler-bank/localhost/testuser-7tgipbkp f \N
-2 \\x1e8f31936b3cee8f8afd3aac9e38b5db42d45b721ffc4eb1e5b9ddaf1565660cb3a8ffd7e9e69c646815045edc179e5e7ea1ecd9584550d202ae951ebd572e98 payto://x-taler-bank/localhost/43 f \N
-3 \\x95fb0cf57b815365bf0ac184db2737810b48d77984df203211df809b8dcbf1ba740d183c7c147cb0f258cfaa2eab507f8f817829704d22a1875ef93a43ad0fe4 payto://x-taler-bank/localhost/testuser-db6y8s2c f \N
-\.
-
-
---
--- Data for Name: work_shards; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.work_shards (shard_serial_id, last_attempt, start_row, end_row, completed, job_name) FROM stdin;
-1 1638356907957128 0 1024 f wirewatch-exchange-account-1
-\.
-
-
---
--- 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', 13, 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_denom_sigs_auditor_denom_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auditor_denom_sigs_auditor_denom_serial_seq', 424, 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: auditors_auditor_uuid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auditors_auditor_uuid_seq', 1, true);
-
-
---
--- 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', 13, 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: denominations_denominations_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.denominations_denominations_serial_seq', 424, true);
-
-
---
--- Name: deposit_confirmations_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.deposit_confirmations_serial_id_seq', 3, true);
-
-
---
--- 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', 22, true);
-
-
---
--- Name: exchange_sign_keys_esk_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.exchange_sign_keys_esk_serial_seq', 5, true);
-
-
---
--- Name: extension_details_extension_details_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.extension_details_extension_details_serial_id_seq', 1, false);
-
-
---
--- Name: known_coins_known_coin_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.known_coins_known_coin_id_seq', 3, true);
-
-
---
--- Name: merchant_accounts_account_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_accounts_account_serial_seq', 1, true);
-
-
---
--- Name: merchant_deposits_deposit_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_deposits_deposit_serial_seq', 3, true);
-
-
---
--- Name: merchant_exchange_signing_keys_signkey_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_exchange_signing_keys_signkey_serial_seq', 5, true);
-
-
---
--- Name: merchant_exchange_wire_fees_wirefee_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_exchange_wire_fees_wirefee_serial_seq', 1, true);
-
-
---
--- Name: merchant_instances_merchant_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_instances_merchant_serial_seq', 1, true);
-
-
---
--- Name: merchant_inventory_product_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_inventory_product_serial_seq', 1, false);
-
-
---
--- Name: merchant_kyc_kyc_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_kyc_kyc_serial_id_seq', 1, true);
-
-
---
--- Name: merchant_orders_order_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_orders_order_serial_seq', 3, true);
-
-
---
--- Name: merchant_refunds_refund_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_refunds_refund_serial_seq', 1, true);
-
-
---
--- Name: merchant_tip_pickups_pickup_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_tip_pickups_pickup_serial_seq', 1, false);
-
-
---
--- Name: merchant_tip_reserves_reserve_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_tip_reserves_reserve_serial_seq', 1, false);
-
-
---
--- Name: merchant_tips_tip_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_tips_tip_serial_seq', 1, false);
-
-
---
--- Name: merchant_transfers_credit_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_transfers_credit_serial_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, 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', 4, true);
-
-
---
--- Name: refresh_revealed_coins_rrc_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.refresh_revealed_coins_rrc_serial_seq', 48, true);
-
-
---
--- Name: refresh_transfer_keys_rtc_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.refresh_transfer_keys_rtc_serial_seq', 4, 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: reserves_reserve_uuid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.reserves_reserve_uuid_seq', 2, true);
-
-
---
--- Name: revolving_work_shards_shard_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.revolving_work_shards_shard_serial_id_seq', 1, false);
-
-
---
--- Name: signkey_revocations_signkey_revocations_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.signkey_revocations_signkey_revocations_serial_id_seq', 1, false);
-
-
---
--- Name: wire_fee_wire_fee_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.wire_fee_wire_fee_serial_seq', 1, 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: wire_targets_wire_target_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.wire_targets_wire_target_serial_id_seq', 3, true);
-
-
---
--- Name: work_shards_shard_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.work_shards_shard_serial_id_seq', 1, true);
-
-
---
--- 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_denom_sigs auditor_denom_sigs_auditor_denom_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denom_sigs
- ADD CONSTRAINT auditor_denom_sigs_auditor_denom_serial_key UNIQUE (auditor_denom_serial);
-
-
---
--- Name: auditor_denom_sigs auditor_denom_sigs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denom_sigs
- ADD CONSTRAINT auditor_denom_sigs_pkey PRIMARY KEY (denominations_serial, auditor_uuid);
-
-
---
--- 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_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: auditors auditors_auditor_uuid_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditors
- ADD CONSTRAINT auditors_auditor_uuid_key UNIQUE (auditor_uuid);
-
-
---
--- Name: auditors auditors_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditors
- ADD CONSTRAINT auditors_pkey PRIMARY KEY (auditor_pub);
-
-
---
--- 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 (denominations_serial);
-
-
---
--- Name: denominations denominations_denominations_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denominations
- ADD CONSTRAINT denominations_denominations_serial_key UNIQUE (denominations_serial);
-
-
---
--- 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_known_coin_id_merchant_pub_h_contract_terms_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits
- ADD CONSTRAINT deposits_known_coin_id_merchant_pub_h_contract_terms_key UNIQUE (known_coin_id, 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_sign_keys exchange_sign_keys_esk_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.exchange_sign_keys
- ADD CONSTRAINT exchange_sign_keys_esk_serial_key UNIQUE (esk_serial);
-
-
---
--- Name: exchange_sign_keys exchange_sign_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.exchange_sign_keys
- ADD CONSTRAINT exchange_sign_keys_pkey PRIMARY KEY (exchange_pub);
-
-
---
--- Name: extension_details extension_details_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.extension_details
- ADD CONSTRAINT extension_details_pkey PRIMARY KEY (extension_details_serial_id);
-
-
---
--- Name: known_coins known_coins_known_coin_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.known_coins
- ADD CONSTRAINT known_coins_known_coin_id_key UNIQUE (known_coin_id);
-
-
---
--- 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_accounts merchant_accounts_h_wire_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_accounts
- ADD CONSTRAINT merchant_accounts_h_wire_key UNIQUE (h_wire);
-
-
---
--- Name: merchant_accounts merchant_accounts_merchant_serial_payto_uri_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_accounts
- ADD CONSTRAINT merchant_accounts_merchant_serial_payto_uri_key UNIQUE (merchant_serial, payto_uri);
-
-
---
--- Name: merchant_accounts merchant_accounts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_accounts
- ADD CONSTRAINT merchant_accounts_pkey PRIMARY KEY (account_serial);
-
-
---
--- Name: merchant_contract_terms merchant_contract_terms_merchant_serial_h_contract_terms_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_contract_terms
- ADD CONSTRAINT merchant_contract_terms_merchant_serial_h_contract_terms_key UNIQUE (merchant_serial, h_contract_terms);
-
-
---
--- Name: merchant_contract_terms merchant_contract_terms_merchant_serial_order_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_contract_terms
- ADD CONSTRAINT merchant_contract_terms_merchant_serial_order_id_key UNIQUE (merchant_serial, order_id);
-
-
---
--- 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_serial);
-
-
---
--- Name: merchant_deposit_to_transfer merchant_deposit_to_transfer_deposit_serial_credit_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposit_to_transfer
- ADD CONSTRAINT merchant_deposit_to_transfer_deposit_serial_credit_serial_key UNIQUE (deposit_serial, credit_serial);
-
-
---
--- Name: merchant_deposits merchant_deposits_order_serial_coin_pub_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits
- ADD CONSTRAINT merchant_deposits_order_serial_coin_pub_key UNIQUE (order_serial, coin_pub);
-
-
---
--- Name: merchant_deposits merchant_deposits_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits
- ADD CONSTRAINT merchant_deposits_pkey PRIMARY KEY (deposit_serial);
-
-
---
--- Name: merchant_exchange_signing_keys merchant_exchange_signing_key_exchange_pub_start_date_maste_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_exchange_signing_keys
- ADD CONSTRAINT merchant_exchange_signing_key_exchange_pub_start_date_maste_key UNIQUE (exchange_pub, start_date, master_pub);
-
-
---
--- Name: merchant_exchange_signing_keys merchant_exchange_signing_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_exchange_signing_keys
- ADD CONSTRAINT merchant_exchange_signing_keys_pkey PRIMARY KEY (signkey_serial);
-
-
---
--- Name: merchant_exchange_wire_fees merchant_exchange_wire_fees_master_pub_h_wire_method_start__key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_exchange_wire_fees
- ADD CONSTRAINT merchant_exchange_wire_fees_master_pub_h_wire_method_start__key UNIQUE (master_pub, h_wire_method, start_date);
-
-
---
--- Name: merchant_exchange_wire_fees merchant_exchange_wire_fees_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_exchange_wire_fees
- ADD CONSTRAINT merchant_exchange_wire_fees_pkey PRIMARY KEY (wirefee_serial);
-
-
---
--- Name: merchant_instances merchant_instances_merchant_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_instances
- ADD CONSTRAINT merchant_instances_merchant_id_key UNIQUE (merchant_id);
-
-
---
--- Name: merchant_instances merchant_instances_merchant_pub_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_instances
- ADD CONSTRAINT merchant_instances_merchant_pub_key UNIQUE (merchant_pub);
-
-
---
--- Name: merchant_instances merchant_instances_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_instances
- ADD CONSTRAINT merchant_instances_pkey PRIMARY KEY (merchant_serial);
-
-
---
--- Name: merchant_inventory merchant_inventory_merchant_serial_product_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_inventory
- ADD CONSTRAINT merchant_inventory_merchant_serial_product_id_key UNIQUE (merchant_serial, product_id);
-
-
---
--- Name: merchant_inventory merchant_inventory_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_inventory
- ADD CONSTRAINT merchant_inventory_pkey PRIMARY KEY (product_serial);
-
-
---
--- Name: merchant_keys merchant_keys_merchant_priv_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_keys
- ADD CONSTRAINT merchant_keys_merchant_priv_key UNIQUE (merchant_priv);
-
-
---
--- Name: merchant_keys merchant_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_keys
- ADD CONSTRAINT merchant_keys_pkey PRIMARY KEY (merchant_serial);
-
-
---
--- Name: merchant_kyc merchant_kyc_kyc_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_kyc
- ADD CONSTRAINT merchant_kyc_kyc_serial_id_key UNIQUE (kyc_serial_id);
-
-
---
--- Name: merchant_kyc merchant_kyc_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_kyc
- ADD CONSTRAINT merchant_kyc_pkey PRIMARY KEY (account_serial, exchange_url);
-
-
---
--- Name: merchant_orders merchant_orders_merchant_serial_order_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_orders
- ADD CONSTRAINT merchant_orders_merchant_serial_order_id_key UNIQUE (merchant_serial, order_id);
-
-
---
--- 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_serial);
-
-
---
--- Name: merchant_refund_proofs merchant_refund_proofs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refund_proofs
- ADD CONSTRAINT merchant_refund_proofs_pkey PRIMARY KEY (refund_serial);
-
-
---
--- Name: merchant_refunds merchant_refunds_order_serial_coin_pub_rtransaction_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refunds
- ADD CONSTRAINT merchant_refunds_order_serial_coin_pub_rtransaction_id_key UNIQUE (order_serial, coin_pub, rtransaction_id);
-
-
---
--- Name: merchant_refunds merchant_refunds_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refunds
- ADD CONSTRAINT merchant_refunds_pkey PRIMARY KEY (refund_serial);
-
-
---
--- Name: merchant_tip_pickup_signatures merchant_tip_pickup_signatures_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_pickup_signatures
- ADD CONSTRAINT merchant_tip_pickup_signatures_pkey PRIMARY KEY (pickup_serial, coin_offset);
-
-
---
--- Name: merchant_tip_pickups merchant_tip_pickups_pickup_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_pickups
- ADD CONSTRAINT merchant_tip_pickups_pickup_id_key UNIQUE (pickup_id);
-
-
---
--- 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_serial);
-
-
---
--- Name: merchant_tip_reserve_keys merchant_tip_reserve_keys_reserve_priv_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserve_keys
- ADD CONSTRAINT merchant_tip_reserve_keys_reserve_priv_key UNIQUE (reserve_priv);
-
-
---
--- Name: merchant_tip_reserve_keys merchant_tip_reserve_keys_reserve_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserve_keys
- ADD CONSTRAINT merchant_tip_reserve_keys_reserve_serial_key UNIQUE (reserve_serial);
-
-
---
--- 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_serial);
-
-
---
--- Name: merchant_tip_reserves merchant_tip_reserves_reserve_pub_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserves
- ADD CONSTRAINT merchant_tip_reserves_reserve_pub_key UNIQUE (reserve_pub);
-
-
---
--- 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_serial);
-
-
---
--- Name: merchant_tips merchant_tips_tip_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tips
- ADD CONSTRAINT merchant_tips_tip_id_key UNIQUE (tip_id);
-
-
---
--- Name: merchant_transfer_signatures merchant_transfer_signatures_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfer_signatures
- ADD CONSTRAINT merchant_transfer_signatures_pkey PRIMARY KEY (credit_serial);
-
-
---
--- Name: merchant_transfer_to_coin merchant_transfer_to_coin_deposit_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfer_to_coin
- ADD CONSTRAINT merchant_transfer_to_coin_deposit_serial_key UNIQUE (deposit_serial);
-
-
---
--- Name: merchant_transfers merchant_transfers_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfers
- ADD CONSTRAINT merchant_transfers_pkey PRIMARY KEY (credit_serial);
-
-
---
--- Name: merchant_transfers merchant_transfers_wtid_exchange_url_account_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfers
- ADD CONSTRAINT merchant_transfers_wtid_exchange_url_account_serial_key UNIQUE (wtid, exchange_url, account_serial);
-
-
---
--- 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: recoup_refresh recoup_refresh_rrc_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup_refresh
- ADD CONSTRAINT recoup_refresh_rrc_serial_key UNIQUE (rrc_serial);
-
-
---
--- 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 (melt_serial_id, freshcoin_index);
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_rrc_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_rrc_serial_key UNIQUE (rrc_serial);
-
-
---
--- 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 (melt_serial_id);
-
-
---
--- Name: refresh_transfer_keys refresh_transfer_keys_rtc_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_transfer_keys
- ADD CONSTRAINT refresh_transfer_keys_rtc_serial_key UNIQUE (rtc_serial);
-
-
---
--- Name: refunds refunds_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refunds
- ADD CONSTRAINT refunds_pkey PRIMARY KEY (deposit_serial_id, 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_uuid, 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: reserves reserves_reserve_uuid_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves
- ADD CONSTRAINT reserves_reserve_uuid_key UNIQUE (reserve_uuid);
-
-
---
--- Name: revolving_work_shards revolving_work_shards_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.revolving_work_shards
- ADD CONSTRAINT revolving_work_shards_pkey PRIMARY KEY (job_name, start_row);
-
-
---
--- Name: revolving_work_shards revolving_work_shards_shard_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.revolving_work_shards
- ADD CONSTRAINT revolving_work_shards_shard_serial_id_key UNIQUE (shard_serial_id);
-
-
---
--- Name: signkey_revocations signkey_revocations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.signkey_revocations
- ADD CONSTRAINT signkey_revocations_pkey PRIMARY KEY (esk_serial);
-
-
---
--- Name: signkey_revocations signkey_revocations_signkey_revocations_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.signkey_revocations
- ADD CONSTRAINT signkey_revocations_signkey_revocations_serial_id_key UNIQUE (signkey_revocations_serial_id);
-
-
---
--- Name: wire_accounts wire_accounts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_accounts
- ADD CONSTRAINT wire_accounts_pkey PRIMARY KEY (payto_uri);
-
-
---
--- 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_fee wire_fee_wire_fee_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_fee
- ADD CONSTRAINT wire_fee_wire_fee_serial_key UNIQUE (wire_fee_serial);
-
-
---
--- 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: wire_targets wire_targets_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_targets
- ADD CONSTRAINT wire_targets_pkey PRIMARY KEY (h_payto);
-
-
---
--- Name: wire_targets wire_targets_wire_target_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_targets
- ADD CONSTRAINT wire_targets_wire_target_serial_id_key UNIQUE (wire_target_serial_id);
-
-
---
--- Name: work_shards work_shards_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.work_shards
- ADD CONSTRAINT work_shards_pkey PRIMARY KEY (job_name, start_row);
-
-
---
--- Name: work_shards work_shards_shard_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.work_shards
- ADD CONSTRAINT work_shards_shard_serial_id_key UNIQUE (shard_serial_id);
-
-
---
--- 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 (known_coin_id, 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 (shard, done, extension_blocked, tiny, wire_deadline);
-
-
---
--- Name: deposits_iterate_matching_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX deposits_iterate_matching_index ON public.deposits USING btree (merchant_pub, wire_target_serial_id, done, extension_blocked, refund_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 (denominations_serial);
-
-
---
--- Name: known_coins_by_hashed_coin_pub; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX known_coins_by_hashed_coin_pub ON public.known_coins USING hash (coin_pub);
-
-
---
--- Name: known_coins_by_hashed_rc; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX known_coins_by_hashed_rc ON public.refresh_commitments USING hash (rc);
-
-
---
--- Name: merchant_contract_terms_by_merchant_and_expiration; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_contract_terms_by_merchant_and_expiration ON public.merchant_contract_terms USING btree (merchant_serial, pay_deadline);
-
-
---
--- Name: merchant_contract_terms_by_merchant_and_payment; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_contract_terms_by_merchant_and_payment ON public.merchant_contract_terms USING btree (merchant_serial, paid);
-
-
---
--- Name: merchant_contract_terms_by_merchant_session_and_fulfillment; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_contract_terms_by_merchant_session_and_fulfillment ON public.merchant_contract_terms USING btree (merchant_serial, fulfillment_url, session_id);
-
-
---
--- Name: merchant_inventory_locks_by_expiration; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_inventory_locks_by_expiration ON public.merchant_inventory_locks USING btree (expiration);
-
-
---
--- Name: merchant_inventory_locks_by_uuid; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_inventory_locks_by_uuid ON public.merchant_inventory_locks USING btree (lock_uuid);
-
-
---
--- Name: merchant_orders_by_creation_time; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_orders_by_creation_time ON public.merchant_orders USING btree (creation_time);
-
-
---
--- Name: merchant_orders_by_expiration; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_orders_by_expiration ON public.merchant_orders USING btree (pay_deadline);
-
-
---
--- Name: merchant_orders_locks_by_order_and_product; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_orders_locks_by_order_and_product ON public.merchant_order_locks USING btree (order_serial, product_serial);
-
-
---
--- Name: merchant_refunds_by_coin_and_order; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_refunds_by_coin_and_order ON public.merchant_refunds USING btree (coin_pub, order_serial);
-
-
---
--- Name: merchant_tip_reserves_by_exchange_balance; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_tip_reserves_by_exchange_balance ON public.merchant_tip_reserves USING btree (exchange_initial_balance_val, exchange_initial_balance_frac);
-
-
---
--- Name: merchant_tip_reserves_by_merchant_serial_and_creation_time; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_tip_reserves_by_merchant_serial_and_creation_time ON public.merchant_tip_reserves USING btree (merchant_serial, creation_time);
-
-
---
--- Name: merchant_tip_reserves_by_reserve_pub_and_merchant_serial; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_tip_reserves_by_reserve_pub_and_merchant_serial ON public.merchant_tip_reserves USING btree (reserve_pub, merchant_serial, creation_time);
-
-
---
--- Name: merchant_tips_by_pickup_and_expiration; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_tips_by_pickup_and_expiration ON public.merchant_tips USING btree (was_picked_up, expiration);
-
-
---
--- Name: merchant_transfers_by_credit; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_transfers_by_credit ON public.merchant_transfer_to_coin USING btree (credit_serial);
-
-
---
--- Name: prepare_get_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX prepare_get_index ON public.prewire USING btree (failed, finished);
-
-
---
--- Name: INDEX prepare_get_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.prepare_get_index IS 'for wire_prepare_data_get';
-
-
---
--- 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 gc_prewire';
-
-
---
--- Name: recoup_by_h_blind_ev; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_by_h_blind_ev ON public.recoup USING btree (reserve_out_serial_id);
-
-
---
--- Name: recoup_for_by_reserve; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_for_by_reserve ON public.recoup USING btree (known_coin_id, reserve_out_serial_id);
-
-
---
--- 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 (rrc_serial);
-
-
---
--- Name: recoup_refresh_for_by_reserve; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_refresh_for_by_reserve ON public.recoup_refresh USING btree (known_coin_id, rrc_serial);
-
-
---
--- Name: refresh_commitments_old_coin_id_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX refresh_commitments_old_coin_id_index ON public.refresh_commitments USING btree (old_known_coin_id);
-
-
---
--- Name: refresh_revealed_coins_denominations_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX refresh_revealed_coins_denominations_index ON public.refresh_revealed_coins USING btree (denominations_serial);
-
-
---
--- 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 (melt_serial_id, 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: reserves_close_by_uuid; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_close_by_uuid ON public.reserves_close USING btree (reserve_uuid);
-
-
---
--- 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 (denominations_serial, h_blind_ev);
-
-
---
--- Name: reserves_out_reserve_uuid_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_out_reserve_uuid_index ON public.reserves_out USING btree (reserve_uuid);
-
-
---
--- Name: INDEX reserves_out_reserve_uuid_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.reserves_out_reserve_uuid_index IS 'for get_reserves_out';
-
-
---
--- Name: revolving_work_shards_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX revolving_work_shards_index ON public.revolving_work_shards USING btree (job_name, active, last_attempt);
-
-
---
--- 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: work_shards_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX work_shards_index ON public.work_shards USING btree (job_name, completed, last_attempt);
-
-
---
--- 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_denom_sigs auditor_denom_sigs_auditor_uuid_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denom_sigs
- ADD CONSTRAINT auditor_denom_sigs_auditor_uuid_fkey FOREIGN KEY (auditor_uuid) REFERENCES public.auditors(auditor_uuid) ON DELETE CASCADE;
-
-
---
--- Name: auditor_denom_sigs auditor_denom_sigs_denominations_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denom_sigs
- ADD CONSTRAINT auditor_denom_sigs_denominations_serial_fkey FOREIGN KEY (denominations_serial) REFERENCES public.denominations(denominations_serial) 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_denominations_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denomination_revocations
- ADD CONSTRAINT denomination_revocations_denominations_serial_fkey FOREIGN KEY (denominations_serial) REFERENCES public.denominations(denominations_serial) ON DELETE CASCADE;
-
-
---
--- Name: deposits deposits_extension_details_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits
- ADD CONSTRAINT deposits_extension_details_serial_id_fkey FOREIGN KEY (extension_details_serial_id) REFERENCES public.extension_details(extension_details_serial_id);
-
-
---
--- Name: deposits deposits_known_coin_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits
- ADD CONSTRAINT deposits_known_coin_id_fkey FOREIGN KEY (known_coin_id) REFERENCES public.known_coins(known_coin_id) ON DELETE CASCADE;
-
-
---
--- Name: deposits deposits_wire_target_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits
- ADD CONSTRAINT deposits_wire_target_serial_id_fkey FOREIGN KEY (wire_target_serial_id) REFERENCES public.wire_targets(wire_target_serial_id);
-
-
---
--- Name: known_coins known_coins_denominations_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.known_coins
- ADD CONSTRAINT known_coins_denominations_serial_fkey FOREIGN KEY (denominations_serial) REFERENCES public.denominations(denominations_serial) 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_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_accounts merchant_accounts_merchant_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_accounts
- ADD CONSTRAINT merchant_accounts_merchant_serial_fkey FOREIGN KEY (merchant_serial) REFERENCES public.merchant_instances(merchant_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_contract_terms merchant_contract_terms_merchant_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_contract_terms
- ADD CONSTRAINT merchant_contract_terms_merchant_serial_fkey FOREIGN KEY (merchant_serial) REFERENCES public.merchant_instances(merchant_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_deposit_to_transfer merchant_deposit_to_transfer_credit_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposit_to_transfer
- ADD CONSTRAINT merchant_deposit_to_transfer_credit_serial_fkey FOREIGN KEY (credit_serial) REFERENCES public.merchant_transfers(credit_serial);
-
-
---
--- Name: merchant_deposit_to_transfer merchant_deposit_to_transfer_deposit_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposit_to_transfer
- ADD CONSTRAINT merchant_deposit_to_transfer_deposit_serial_fkey FOREIGN KEY (deposit_serial) REFERENCES public.merchant_deposits(deposit_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_deposit_to_transfer merchant_deposit_to_transfer_signkey_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposit_to_transfer
- ADD CONSTRAINT merchant_deposit_to_transfer_signkey_serial_fkey FOREIGN KEY (signkey_serial) REFERENCES public.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_deposits merchant_deposits_account_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits
- ADD CONSTRAINT merchant_deposits_account_serial_fkey FOREIGN KEY (account_serial) REFERENCES public.merchant_accounts(account_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_deposits merchant_deposits_order_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits
- ADD CONSTRAINT merchant_deposits_order_serial_fkey FOREIGN KEY (order_serial) REFERENCES public.merchant_contract_terms(order_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_deposits merchant_deposits_signkey_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits
- ADD CONSTRAINT merchant_deposits_signkey_serial_fkey FOREIGN KEY (signkey_serial) REFERENCES public.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_inventory_locks merchant_inventory_locks_product_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_inventory_locks
- ADD CONSTRAINT merchant_inventory_locks_product_serial_fkey FOREIGN KEY (product_serial) REFERENCES public.merchant_inventory(product_serial);
-
-
---
--- Name: merchant_inventory merchant_inventory_merchant_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_inventory
- ADD CONSTRAINT merchant_inventory_merchant_serial_fkey FOREIGN KEY (merchant_serial) REFERENCES public.merchant_instances(merchant_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_keys merchant_keys_merchant_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_keys
- ADD CONSTRAINT merchant_keys_merchant_serial_fkey FOREIGN KEY (merchant_serial) REFERENCES public.merchant_instances(merchant_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_kyc merchant_kyc_account_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_kyc
- ADD CONSTRAINT merchant_kyc_account_serial_fkey FOREIGN KEY (account_serial) REFERENCES public.merchant_accounts(account_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_order_locks merchant_order_locks_order_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_order_locks
- ADD CONSTRAINT merchant_order_locks_order_serial_fkey FOREIGN KEY (order_serial) REFERENCES public.merchant_orders(order_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_order_locks merchant_order_locks_product_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_order_locks
- ADD CONSTRAINT merchant_order_locks_product_serial_fkey FOREIGN KEY (product_serial) REFERENCES public.merchant_inventory(product_serial);
-
-
---
--- Name: merchant_orders merchant_orders_merchant_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_orders
- ADD CONSTRAINT merchant_orders_merchant_serial_fkey FOREIGN KEY (merchant_serial) REFERENCES public.merchant_instances(merchant_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_refund_proofs merchant_refund_proofs_refund_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refund_proofs
- ADD CONSTRAINT merchant_refund_proofs_refund_serial_fkey FOREIGN KEY (refund_serial) REFERENCES public.merchant_refunds(refund_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_refund_proofs merchant_refund_proofs_signkey_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refund_proofs
- ADD CONSTRAINT merchant_refund_proofs_signkey_serial_fkey FOREIGN KEY (signkey_serial) REFERENCES public.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_refunds merchant_refunds_order_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refunds
- ADD CONSTRAINT merchant_refunds_order_serial_fkey FOREIGN KEY (order_serial) REFERENCES public.merchant_contract_terms(order_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_tip_pickup_signatures merchant_tip_pickup_signatures_pickup_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_pickup_signatures
- ADD CONSTRAINT merchant_tip_pickup_signatures_pickup_serial_fkey FOREIGN KEY (pickup_serial) REFERENCES public.merchant_tip_pickups(pickup_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_tip_pickups merchant_tip_pickups_tip_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_pickups
- ADD CONSTRAINT merchant_tip_pickups_tip_serial_fkey FOREIGN KEY (tip_serial) REFERENCES public.merchant_tips(tip_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_tip_reserve_keys merchant_tip_reserve_keys_reserve_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserve_keys
- ADD CONSTRAINT merchant_tip_reserve_keys_reserve_serial_fkey FOREIGN KEY (reserve_serial) REFERENCES public.merchant_tip_reserves(reserve_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_tip_reserves merchant_tip_reserves_merchant_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserves
- ADD CONSTRAINT merchant_tip_reserves_merchant_serial_fkey FOREIGN KEY (merchant_serial) REFERENCES public.merchant_instances(merchant_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_tips merchant_tips_reserve_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tips
- ADD CONSTRAINT merchant_tips_reserve_serial_fkey FOREIGN KEY (reserve_serial) REFERENCES public.merchant_tip_reserves(reserve_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_transfer_signatures merchant_transfer_signatures_credit_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfer_signatures
- ADD CONSTRAINT merchant_transfer_signatures_credit_serial_fkey FOREIGN KEY (credit_serial) REFERENCES public.merchant_transfers(credit_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_transfer_signatures merchant_transfer_signatures_signkey_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfer_signatures
- ADD CONSTRAINT merchant_transfer_signatures_signkey_serial_fkey FOREIGN KEY (signkey_serial) REFERENCES public.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_transfer_to_coin merchant_transfer_to_coin_credit_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfer_to_coin
- ADD CONSTRAINT merchant_transfer_to_coin_credit_serial_fkey FOREIGN KEY (credit_serial) REFERENCES public.merchant_transfers(credit_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_transfer_to_coin merchant_transfer_to_coin_deposit_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfer_to_coin
- ADD CONSTRAINT merchant_transfer_to_coin_deposit_serial_fkey FOREIGN KEY (deposit_serial) REFERENCES public.merchant_deposits(deposit_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_transfers merchant_transfers_account_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfers
- ADD CONSTRAINT merchant_transfers_account_serial_fkey FOREIGN KEY (account_serial) REFERENCES public.merchant_accounts(account_serial) ON DELETE CASCADE;
-
-
---
--- Name: recoup recoup_known_coin_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup
- ADD CONSTRAINT recoup_known_coin_id_fkey FOREIGN KEY (known_coin_id) REFERENCES public.known_coins(known_coin_id);
-
-
---
--- Name: recoup_refresh recoup_refresh_known_coin_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup_refresh
- ADD CONSTRAINT recoup_refresh_known_coin_id_fkey FOREIGN KEY (known_coin_id) REFERENCES public.known_coins(known_coin_id);
-
-
---
--- Name: recoup_refresh recoup_refresh_rrc_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup_refresh
- ADD CONSTRAINT recoup_refresh_rrc_serial_fkey FOREIGN KEY (rrc_serial) REFERENCES public.refresh_revealed_coins(rrc_serial) ON DELETE CASCADE;
-
-
---
--- Name: recoup recoup_reserve_out_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup
- ADD CONSTRAINT recoup_reserve_out_serial_id_fkey FOREIGN KEY (reserve_out_serial_id) REFERENCES public.reserves_out(reserve_out_serial_id) ON DELETE CASCADE;
-
-
---
--- Name: refresh_commitments refresh_commitments_old_known_coin_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_commitments
- ADD CONSTRAINT refresh_commitments_old_known_coin_id_fkey FOREIGN KEY (old_known_coin_id) REFERENCES public.known_coins(known_coin_id) ON DELETE CASCADE;
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_denominations_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_denominations_serial_fkey FOREIGN KEY (denominations_serial) REFERENCES public.denominations(denominations_serial) ON DELETE CASCADE;
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_melt_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_melt_serial_id_fkey FOREIGN KEY (melt_serial_id) REFERENCES public.refresh_commitments(melt_serial_id) ON DELETE CASCADE;
-
-
---
--- Name: refresh_transfer_keys refresh_transfer_keys_melt_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_transfer_keys
- ADD CONSTRAINT refresh_transfer_keys_melt_serial_id_fkey FOREIGN KEY (melt_serial_id) REFERENCES public.refresh_commitments(melt_serial_id) ON DELETE CASCADE;
-
-
---
--- Name: refunds refunds_deposit_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refunds
- ADD CONSTRAINT refunds_deposit_serial_id_fkey FOREIGN KEY (deposit_serial_id) REFERENCES public.deposits(deposit_serial_id) ON DELETE CASCADE;
-
-
---
--- Name: reserves_close reserves_close_reserve_uuid_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_close
- ADD CONSTRAINT reserves_close_reserve_uuid_fkey FOREIGN KEY (reserve_uuid) REFERENCES public.reserves(reserve_uuid) ON DELETE CASCADE;
-
-
---
--- Name: reserves_close reserves_close_wire_target_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_close
- ADD CONSTRAINT reserves_close_wire_target_serial_id_fkey FOREIGN KEY (wire_target_serial_id) REFERENCES public.wire_targets(wire_target_serial_id);
-
-
---
--- Name: reserves_in reserves_in_reserve_uuid_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_in
- ADD CONSTRAINT reserves_in_reserve_uuid_fkey FOREIGN KEY (reserve_uuid) REFERENCES public.reserves(reserve_uuid) ON DELETE CASCADE;
-
-
---
--- Name: reserves_in reserves_in_wire_source_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_in
- ADD CONSTRAINT reserves_in_wire_source_serial_id_fkey FOREIGN KEY (wire_source_serial_id) REFERENCES public.wire_targets(wire_target_serial_id);
-
-
---
--- Name: reserves_out reserves_out_denominations_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_out
- ADD CONSTRAINT reserves_out_denominations_serial_fkey FOREIGN KEY (denominations_serial) REFERENCES public.denominations(denominations_serial);
-
-
---
--- Name: reserves_out reserves_out_reserve_uuid_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_out
- ADD CONSTRAINT reserves_out_reserve_uuid_fkey FOREIGN KEY (reserve_uuid) REFERENCES public.reserves(reserve_uuid) ON DELETE CASCADE;
-
-
---
--- Name: signkey_revocations signkey_revocations_esk_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.signkey_revocations
- ADD CONSTRAINT signkey_revocations_esk_serial_fkey FOREIGN KEY (esk_serial) REFERENCES public.exchange_sign_keys(esk_serial) 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;
-
-
---
--- Name: wire_out wire_out_wire_target_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_out
- ADD CONSTRAINT wire_out_wire_target_serial_id_fkey FOREIGN KEY (wire_target_serial_id) REFERENCES public.wire_targets(wire_target_serial_id);
-
-
---
--- PostgreSQL database dump complete
---
-
diff --git a/src/auditor/auditor.conf b/src/auditor/auditor.conf
index 270836283..3c04d196f 100644
--- a/src/auditor/auditor.conf
+++ b/src/auditor/auditor.conf
@@ -9,7 +9,7 @@ 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.
@@ -17,7 +17,7 @@ AUDITOR_PRIV_FILE = ${TALER_DATA_HOME}/auditor/offline-keys/auditor.priv
# What is the Web site of the auditor (i.e. to file complaints about
# a misbehaving exchange)?
-# BASE_URL = https://auditor.taler.net/
+BASE_URL = http://localhost:8083/
# Network configuration for the normal API/service HTTP server
@@ -26,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 e5de0b592..8cf63fbba 100644
--- a/src/auditor/generate-auditor-basedb.conf
+++ b/src/auditor/generate-auditor-basedb.conf
@@ -1,101 +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]
-MAX_KEYS_CACHING = forever
-DB = postgres
-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
-SIGNKEY_LEGAL_DURATION = 4 weeks
LOOKAHEAD_SIGN = 32 weeks 1 day
+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
-[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
-
-[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
-BASE_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/
+[libeufin-bankdb-postgres]
+CONFIG = postgresql:///auditor-basedb
-[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
-SERVE = http
-
-[exchangedb]
+[exchangedb-postgres]
+CONFIG = postgres:///auditor-basedb
+SQL_DIR = $DATADIR/sql/exchange/
IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
-LEGAL_RESERVE_EXPIRATION_TIME = 7 years
-
-[taler]
-CURRENCY = TESTKUDOS
-CURRENCY_ROUND_UNIT = TESTKUDOS:0.01
+LEGAL_RESERVE_EXPIRATION_TIME = 4 weeks
[exchange-account-1]
-PAYTO_URI = payto://x-taler-bank/localhost/Exchange
-enable_debit = yes
-enable_credit = yes
+PAYTO_URI = payto://iban/SANDBOXX/DE989651?receiver-name=Exchange+Company
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
[exchange-accountcredentials-1]
-WIRE_GATEWAY_URL = "http://localhost:8082/taler-wire-gateway/Exchange/"
+WIRE_GATEWAY_URL = http://localhost:8082/accounts/exchange/taler-wire-gateway/
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = exchange
+PASSWORD = x
+
+[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
+# 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-account-merchant]
-PAYTO_URI = payto://x-taler-bank/localhost/42
-HONOR_default = YES
-ACTIVE_default = YES
+
+[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
@@ -106,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]
@@ -117,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]
@@ -128,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]
@@ -139,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]
@@ -150,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]
@@ -161,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]
@@ -172,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]
@@ -183,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 140c6237e..bbce37cdc 100755
--- a/src/auditor/generate-auditor-basedb.sh
+++ b/src/auditor/generate-auditor-basedb.sh
@@ -1,217 +1,114 @@
#!/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
-# 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
-}
+. 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
-
-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=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 --help >/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-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`
-
-# 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
-
-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-bank-manage-testing $CONF postgres:///$TARGET_DB serve &> 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-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
+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 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 \
- upload &> taler-exchange-offline.log
-
-echo -n "."
-
-for n in `seq 1 2`
-do
- echo -n "."
- OK=0
- # bank
- wget --timeout=1 http://localhost:8081/keys -o /dev/null -O /dev/null >/dev/null || continue
- OK=1
- break
-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
- exit_skip "Failed to setup keys"
+ exit_fail "Expected 200 OK. Got: $STATUS"
fi
-
-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"},"payto_uris":["payto://x-taler-bank/localhost/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_ms" : 3600000},"default_pay_delay":{"d_ms": 3600000}}' http://localhost:9966/management/instances
-
-
echo " DONE"
-# run wallet CLI
-echo "Running wallet"
-
-
-taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api 'runIntegrationTest' \
+# 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",
- bankBaseUrl: $BANK_URL,
+ corebankApiBaseUrl: $BANK_URL,
exchangeBaseUrl: $EXCHANGE_URL,
merchantBaseUrl: $MERCHANT_URL,
}' \
@@ -219,28 +116,28 @@ taler-wallet-cli --no-throttle --wallet-db=$WALLET_DB api 'runIntegrationTest' \
--arg EXCHANGE_URL "$EXCHANGE_URL" \
--arg BANK_URL "$BANK_URL"
)" &> taler-wallet-cli.log
+echo " DONE"
-
-echo "Shutting down services"
-cleanup
+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")"
-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 ef297e7ab..29aa74b27 100755
--- a/src/auditor/generate-revoke-basedb.sh
+++ b/src/auditor/generate-revoke-basedb.sh
@@ -6,257 +6,138 @@
# create/drop databases.
#
set -eu
+# set -x
-# 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
-}
+. setup.sh
-# 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 --help >/dev/null </dev/null || exit_skip " MISSING"
+echo -n "Testing for curl ..."
+curl --help >/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"
-
-
-# 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"
-
-# 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
-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/
-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`
-
-# 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
-
-echo "Setting up merchant"
-taler-merchant-dbinit -c $CONF
-
-# setup auditor
-echo "Setting up auditor"
-taler-auditor-dbinit -c $CONF
-taler-auditor-exchange -c $CONF -m $MASTER_PUB -u $EXCHANGE_URL
-
-# Launch services
-echo "Launching services"
-taler-bank-manage-testing $CONF postgres:///$TARGET_DB serve &> revocation-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 &
-SIGNKEY_HELPER_PID=$!
-taler-exchange-secmod-rsa -c $CONF 2> taler-exchange-secmod-rsa.log &
-DENOM_HELPER_PID=$!
-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 &
-
-# 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 other 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
- cleanup
- exit_skip "Failed to launch services"
-fi
+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"
-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 \
- upload &> taler-exchange-offline.log
-
-echo -n "."
-
-for n in `seq 1 2`
-do
- echo -n "."
- OK=0
- # bank
- 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
-
+# Launch exchange, merchant and bank.
+setup -c "$CONF" \
+ -abemw \
+ -d "iban"
-taler-auditor-offline -c $CONF \
- download sign upload &> taler-auditor-offline.log
+# 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}"
-echo " DONE"
# Setup merchant
-echo -n "Setting up merchant"
-
-curl -H "Content-Type: application/json" -X POST -d '{"auth": {"method": "external"}, "payto_uris":["payto://x-taler-bank/localhost/43"],"id":"default","name":"default","address":{},"jurisdiction":{},"default_max_wire_fee":"TESTKUDOS:1", "default_max_deposit_fee":"TESTKUDOS:1","default_wire_fee_amortization":1,"default_wire_transfer_delay":{"d_ms" : 3600000},"default_pay_delay":{"d_ms": 3600000}}' http://localhost:9966/management/instances
+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 --no-throttle --wallet-db=$WALLET_DB api 'withdrawTestBalance' \
+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",
- bankBaseUrl: $BANK_URL,
+ corebankApiBaseUrl: $BANK_URL,
exchangeBaseUrl: $EXCHANGE_URL,
}' \
- --arg BANK_URL $BANK_URL \
- --arg EXCHANGE_URL $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 \
+ --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)
+export COINS=$(taler-wallet-cli --wallet-db="$WALLET_DB" advanced dump-coins)
echo -n "COINS are:"
-echo $coins
+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-offline -c $CONF \
- revoke-denomination "${rd}" upload &> taler-exchange-offline-revoke.log
-
+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.log
+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 --no-throttle --wallet-db=$WALLET_DB api 'testPay' \
+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
+ --arg MERCHANT_URL "$MERCHANT_URL"
)"
-taler-wallet-cli --wallet-db=$WALLET_DB run-until-done
+taler-wallet-cli \
+ --wallet-db="$WALLET_DB" \
+ run-until-done
echo "Purchase with recoup'ed coin (via reserve) done"
@@ -269,21 +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
-kill -TERM $DENOM_HELPER_PID
+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> taler-exchange-secmod-eddsa.log &
+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> taler-exchange-secmod-rsa.log &
-DENOM_HELPER_PID=$!
-taler-exchange-httpd $TIMETRAVEL -c $CONF 2> taler-exchange-httpd.log &
+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
@@ -299,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)
@@ -323,29 +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-offline -c $CONF \
- revoke-denomination "${fresh_denom}" upload &> taler-exchange-offline-revoke-2.log
+taler-exchange-offline \
+ -c "$CONF" \
+ revoke-denomination \
+ "${fresh_denom}" \
+ upload &> taler-exchange-offline-revoke-2.log
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
+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
@@ -369,30 +277,35 @@ taler-wallet-cli $TIMETRAVEL --no-throttle --wallet-db=$WALLET_DB api 'testPay'
}' \
--arg MERCHANT_URL $MERCHANT_URL
)"
-taler-wallet-cli $TIMETRAVEL --wallet-db=$WALLET_DB run-until-done
+taler-wallet-cli \
+ "$TIMETRAVEL" \
+ --wallet-db="$WALLET_DB" \
+ run-until-done
echo "Bought something with refresh-recouped coin"
echo "Shutting down services"
-cleanup
+exit_cleanup
-# Dump database
-echo "Dumping database"
-pg_dump -O $TARGET_DB | sed -e '/AS integer/d' > ${BASEDB}.sql
+# Where do we write the result?
+export BASEDB=${1:-"revoke-basedb"}
-echo $MASTER_PUB > ${BASEDB}.mpub
-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"
-dropdb $TARGET_DB
-rm -rf $DATA_DIR || true
-rm -f $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 0dea786da..d0e1325ea 100644
--- a/src/auditor/report-lib.c
+++ b/src/auditor/report-lib.c
@@ -62,13 +62,18 @@ struct TALER_AuditorPublicKeyP TALER_ARL_auditor_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?
*/
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;
@@ -89,13 +94,6 @@ static struct GNUNET_SIGNAL_Context *sig_int;
static struct GNUNET_SIGNAL_Context *sig_term;
-/**
- * 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)
{
@@ -103,12 +101,6 @@ TALER_ARL_do_abort (void)
}
-/**
- * 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)
@@ -127,16 +119,14 @@ TALER_ARL_report (json_t *array,
*
* @param cls closure, NULL
* @param denom_pub public key, sometimes NULL (!)
- * @param validity issuing information with value, fees and other info about the denomination.
+ * @param issue issuing information with value, fees and other info about the denomination.
*/
static void
add_denomination (
void *cls,
const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *validity)
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue)
{
- const struct TALER_DenominationKeyValidityPS *issue = &validity->properties;
-
(void) cls;
(void) denom_pub;
if (NULL !=
@@ -145,35 +135,26 @@ add_denomination (
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.hash),
- TALER_amount2s (&value));
- TALER_amount_ntoh (&value,
- &issue->fee_withdraw);
+ 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_TIME_timestamp2s
- (GNUNET_TIME_timestamp_ntoh (issue->start)));
+ GNUNET_TIME_timestamp2s (issue->start));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Expire deposit time is %s\n",
- GNUNET_TIME_timestamp2s
- (GNUNET_TIME_timestamp_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;
- i->master = TALER_ARL_master_pub;
GNUNET_assert (GNUNET_OK ==
GNUNET_CONTAINER_multihashmap_put (denominations,
&issue->denom_hash.hash,
@@ -183,18 +164,10 @@ add_denomination (
}
-/**
- * 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 TALER_DenominationHash *dh,
- const struct TALER_DenominationKeyValidityPS **issue)
+ const struct TALER_DenominationHashP *dh,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation **issue)
{
enum GNUNET_DB_QueryStatus qs;
@@ -207,12 +180,13 @@ TALER_ARL_get_denomination_info_by_hash (
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->hash);
@@ -225,13 +199,14 @@ TALER_ARL_get_denomination_info_by_hash (
}
/* maybe database changed since we last iterated, give it one more shot */
{
- struct TALER_EXCHANGEDB_DenominationKeyInformationP issue;
+ 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",
@@ -243,7 +218,7 @@ TALER_ARL_get_denomination_info_by_hash (
&issue);
}
{
- const struct TALER_DenominationKeyValidityPS *i;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *i;
i = GNUNET_CONTAINER_multihashmap_get (denominations,
&dh->hash);
@@ -262,22 +237,13 @@ TALER_ARL_get_denomination_info_by_hash (
}
-/**
- * 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 TALER_DenominationHash *dh)
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation **issue,
+ struct TALER_DenominationHashP *dh)
{
- struct TALER_DenominationHash hc;
+ struct TALER_DenominationHashP hc;
if (NULL == dh)
dh = &hc;
@@ -294,11 +260,11 @@ 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)
{
@@ -369,14 +335,7 @@ 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)
{
@@ -402,41 +361,6 @@ 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)
-{
- int *found = cls;
-
- (void) exchange_url;
- if (0 == GNUNET_memcmp (mpub,
- &TALER_ARL_master_pub))
- *found = GNUNET_YES;
-}
-
-
-/**
- * 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,
@@ -485,19 +409,6 @@ TALER_ARL_amount_add_ (struct TALER_Amount *sum,
}
-/**
- * Perform subtraction of amounts. 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,
@@ -546,24 +457,6 @@ TALER_ARL_amount_subtract_ (struct TALER_Amount *diff,
}
-/**
- * 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,
@@ -622,13 +515,7 @@ handle_sigint (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)
{
TALER_ARL_cfg = c;
@@ -645,7 +532,19 @@ TALER_ARL_init (const struct GNUNET_CONFIGURATION_Handle *c)
"BASE_URL");
return GNUNET_SYSERR;
}
- if (GNUNET_YES == GNUNET_is_zero (&TALER_ARL_master_pub))
+ 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 *master_public_key_str;
@@ -676,6 +575,9 @@ TALER_ARL_init (const struct GNUNET_CONFIGURATION_Handle *c)
GNUNET_free (master_public_key_str);
return GNUNET_SYSERR;
}
+ 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 */
@@ -683,8 +585,36 @@ TALER_ARL_init (const struct GNUNET_CONFIGURATION_Handle *c)
"Taler auditor running for exchange master public key %s\n",
TALER_B2S (&TALER_ARL_master_pub));
- if (GNUNET_YES == GNUNET_is_zero (&TALER_ARL_auditor_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;
@@ -696,6 +626,9 @@ TALER_ARL_init (const struct GNUNET_CONFIGURATION_Handle *c)
{
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! */
@@ -708,36 +641,12 @@ TALER_ARL_init (const struct GNUNET_CONFIGURATION_Handle *c)
}
}
- if (GNUNET_YES == GNUNET_is_zero (&TALER_ARL_auditor_pub))
+ if (GNUNET_is_zero (&TALER_ARL_auditor_pub))
{
- /* private key not available, try configuration for public key */
- char *auditor_public_key_str;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (c,
- "auditor",
- "PUBLIC_KEY",
- &auditor_public_key_str))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "auditor",
- "PUBLIC_KEY");
- return GNUNET_SYSERR;
- }
- 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);
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO,
+ "auditor",
+ "PUBLIC_KEY/AUDITOR_PRIV_FILE");
+ return GNUNET_SYSERR;
}
if (GNUNET_OK !=
@@ -794,39 +703,18 @@ 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))
{
- int found;
-
- if (GNUNET_SYSERR ==
- TALER_ARL_adb->preflight (TALER_ARL_adb->cls))
- {
- 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,
- &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)
{
@@ -859,6 +747,7 @@ 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 3f4489763..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
@@ -75,6 +121,11 @@ extern struct TALER_AuditorPublicKeyP TALER_ARL_auditor_pub;
extern char *TALER_ARL_auditor_url;
/**
+ * REST API endpoint of the exchange.
+ */
+extern char *TALER_ARL_exchange_url;
+
+/**
* At what time did the auditor process start?
*/
extern struct GNUNET_TIME_Absolute start_time;
@@ -101,8 +152,8 @@ TALER_ARL_report (json_t *array,
*/
enum GNUNET_DB_QueryStatus
TALER_ARL_get_denomination_info_by_hash (
- const struct TALER_DenominationHash *dh,
- const struct TALER_DenominationKeyValidityPS **issue);
+ const struct TALER_DenominationHashP *dh,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation **issue);
/**
@@ -117,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 TALER_DenominationHash *dh);
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation **issue,
+ struct TALER_DenominationHashP *dh);
/**
@@ -276,7 +327,7 @@ TALER_ARL_amount_subtract_neg_ (struct TALER_Amount *diff,
* @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);
@@ -298,7 +349,7 @@ TALER_ARL_do_abort (void);
* @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 e9f0306cc..000000000
--- a/src/auditor/revoke-basedb.age
+++ /dev/null
@@ -1 +0,0 @@
-1638367019
diff --git a/src/auditor/test-auditor.conf b/src/auditor/revoke-basedb.conf
index da440c60a..706f97347 100644
--- a/src/auditor/test-auditor.conf
+++ b/src/auditor/revoke-basedb.conf
@@ -1,34 +1,34 @@
[auditor]
-DB = postgres
+PUBLIC_KEY = CK4P6P5VXR82B1A4C3PY5DCHG8HDZA1HSZR76Z8D6FD57MASFT70
TINY_AMOUNT = TESTKUDOS:0.01
BASE_URL = http://localhost:8083/
[exchange-account-1]
-PAYTO_URI = payto://x-taler-bank/localhost/Exchange
+PAYTO_URI = payto://iban/SANDBOXX/DE717324?receiver-name=Exchange+Company
enable_debit = yes
enable_credit = yes
[exchange-accountcredentials-1]
-WIRE_GATEWAY_URL = "http://localhost:8082/taler-wire-gateway/Exchange/"
+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
diff --git a/src/auditor/revoke-basedb.fees b/src/auditor/revoke-basedb.fees
deleted file mode 100644
index d9b45a374..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 ce92ea22e..000000000
--- a/src/auditor/revoke-basedb.mpub
+++ /dev/null
@@ -1 +0,0 @@
-93PR4M9WR54W046A4DWR58V28T2G94DJCWHY0NMBW2S8N6W60AT0
diff --git a/src/auditor/revoke-basedb.sql b/src/auditor/revoke-basedb.sql
deleted file mode 100644
index 207275c80..000000000
--- a/src/auditor/revoke-basedb.sql
+++ /dev/null
@@ -1,8606 +0,0 @@
---
--- PostgreSQL database dump
---
-
--- Dumped from database version 13.4
--- Dumped by pg_dump version 13.4
-
-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 xmloption = content;
-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: 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_table_access_method = heap;
-
---
--- 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: COLUMN aggregation_tracking.wtid_raw; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.aggregation_tracking.wtid_raw IS 'We first create entries in the aggregation_tracking table and then finally the wire_out entry once we know the total amount. Hence the constraint must be deferrable and we cannot use a wireout_uuid here, because we do not have it when these rows are created. Changing the logic to first INSERT a dummy row into wire_out and then UPDATEing that row in the same transaction would theoretically reduce per-deposit storage costs by 5 percent (24/~460 bytes).';
-
-
---
--- 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 bigint 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 NOT NULL,
- 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_denom_sigs; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_denom_sigs (
- auditor_denom_serial bigint NOT NULL,
- auditor_uuid bigint NOT NULL,
- denominations_serial bigint NOT NULL,
- auditor_sig bytea,
- CONSTRAINT auditor_denom_sigs_auditor_sig_check CHECK ((length(auditor_sig) = 64))
-);
-
-
---
--- Name: TABLE auditor_denom_sigs; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.auditor_denom_sigs IS 'Table with auditor signatures on exchange denomination keys.';
-
-
---
--- Name: COLUMN auditor_denom_sigs.auditor_uuid; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditor_denom_sigs.auditor_uuid IS 'Identifies the auditor.';
-
-
---
--- Name: COLUMN auditor_denom_sigs.denominations_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditor_denom_sigs.denominations_serial IS 'Denomination the signature is for.';
-
-
---
--- Name: COLUMN auditor_denom_sigs.auditor_sig; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditor_denom_sigs.auditor_sig IS 'Signature of the auditor, of purpose TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS.';
-
-
---
--- Name: auditor_denom_sigs_auditor_denom_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auditor_denom_sigs_auditor_denom_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auditor_denom_sigs_auditor_denom_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auditor_denom_sigs_auditor_denom_serial_seq OWNED BY public.auditor_denom_sigs.auditor_denom_serial;
-
-
---
--- 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,
- CONSTRAINT auditor_denomination_pending_denom_pub_hash_check CHECK ((length(denom_pub_hash) = 64))
-);
-
-
---
--- 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_exchange_signkeys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditor_exchange_signkeys (
- master_pub bytea NOT NULL,
- 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 NOT NULL,
- 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 NOT NULL,
- 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 NOT NULL,
- 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 NOT NULL,
- 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 NOT NULL,
- 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 NOT NULL,
- 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: auditors; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.auditors (
- auditor_uuid bigint NOT NULL,
- auditor_pub bytea NOT NULL,
- auditor_name character varying NOT NULL,
- auditor_url character varying NOT NULL,
- is_active boolean NOT NULL,
- last_change bigint NOT NULL,
- CONSTRAINT auditors_auditor_pub_check CHECK ((length(auditor_pub) = 32))
-);
-
-
---
--- Name: TABLE auditors; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.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.';
-
-
---
--- Name: COLUMN auditors.auditor_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditors.auditor_pub IS 'Public key of the auditor.';
-
-
---
--- Name: COLUMN auditors.auditor_url; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditors.auditor_url IS 'The base URL of the auditor.';
-
-
---
--- Name: COLUMN auditors.is_active; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditors.is_active IS 'true if we are currently supporting the use of this auditor.';
-
-
---
--- Name: COLUMN auditors.last_change; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.auditors.last_change IS 'Latest time when active status changed. Used to detect replays of old messages.';
-
-
---
--- Name: auditors_auditor_uuid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.auditors_auditor_uuid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: auditors_auditor_uuid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.auditors_auditor_uuid_seq OWNED BY public.auditors.auditor_uuid;
-
-
---
--- 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 bigint 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(150) 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 bigint 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 bigint 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,
- denominations_serial bigint 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 (
- denominations_serial bigint NOT NULL,
- denom_pub_hash bytea NOT NULL,
- denom_type integer DEFAULT 1 NOT NULL,
- age_restrictions integer DEFAULT 0 NOT NULL,
- denom_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_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 valid denominations the exchange knows about.';
-
-
---
--- Name: COLUMN denominations.denominations_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.denominations.denominations_serial IS 'needed for exchange-auditor replication logic';
-
-
---
--- Name: COLUMN denominations.denom_type; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.denominations.denom_type IS 'determines cipher type for blind signatures used with this denomination; 0 is for RSA';
-
-
---
--- Name: COLUMN denominations.age_restrictions; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.denominations.age_restrictions IS 'bitmask with the age restrictions that are being used for this denomination; 0 if denomination does not support the use of age restrictions';
-
-
---
--- Name: denominations_denominations_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.denominations_denominations_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: denominations_denominations_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.denominations_denominations_serial_seq OWNED BY public.denominations.denominations_serial;
-
-
---
--- Name: deposit_confirmations; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.deposit_confirmations (
- master_pub bytea NOT NULL,
- serial_id bigint NOT NULL,
- h_contract_terms bytea NOT NULL,
- h_extensions bytea NOT NULL,
- h_wire bytea NOT NULL,
- exchange_timestamp bigint NOT NULL,
- refund_deadline bigint NOT NULL,
- wire_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_contract_terms_check1 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,
- shard bigint NOT NULL,
- known_coin_id bigint NOT NULL,
- amount_with_fee_val bigint NOT NULL,
- amount_with_fee_frac integer NOT NULL,
- wallet_timestamp bigint NOT NULL,
- exchange_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,
- coin_sig bytea NOT NULL,
- wire_salt bytea NOT NULL,
- wire_target_serial_id bigint NOT NULL,
- tiny boolean DEFAULT false NOT NULL,
- done boolean DEFAULT false NOT NULL,
- extension_blocked boolean DEFAULT false NOT NULL,
- extension_details_serial_id bigint,
- CONSTRAINT deposits_coin_sig_check CHECK ((length(coin_sig) = 64)),
- CONSTRAINT deposits_h_contract_terms_check CHECK ((length(h_contract_terms) = 64)),
- CONSTRAINT deposits_merchant_pub_check CHECK ((length(merchant_pub) = 32)),
- CONSTRAINT deposits_wire_salt_check CHECK ((length(wire_salt) = 16))
-);
-
-
---
--- 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.shard; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.deposits.shard IS 'Used for load sharding. Should be set based on h_payto and merchant_pub. 64-bit value because we need an *unsigned* 32-bit value.';
-
-
---
--- Name: COLUMN deposits.wire_salt; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.deposits.wire_salt IS 'Salt used when hashing the payto://-URI to get the h_wire';
-
-
---
--- Name: COLUMN deposits.wire_target_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.deposits.wire_target_serial_id IS 'Identifies the target bank account and KYC status';
-
-
---
--- 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: COLUMN deposits.extension_blocked; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.deposits.extension_blocked IS 'True if the aggregation of the deposit is currently blocked by some extension mechanism. Used to filter out deposits that must not be processed by the canonical deposit logic.';
-
-
---
--- Name: COLUMN deposits.extension_details_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.deposits.extension_details_serial_id IS 'References extensions table, NULL if extensions are not used';
-
-
---
--- 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 bigint 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_sign_keys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.exchange_sign_keys (
- esk_serial bigint NOT NULL,
- exchange_pub bytea NOT NULL,
- master_sig bytea NOT NULL,
- valid_from bigint NOT NULL,
- expire_sign bigint NOT NULL,
- expire_legal bigint NOT NULL,
- CONSTRAINT exchange_sign_keys_exchange_pub_check CHECK ((length(exchange_pub) = 32)),
- CONSTRAINT exchange_sign_keys_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE exchange_sign_keys; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.exchange_sign_keys IS 'Table with master public key signatures on exchange online signing keys.';
-
-
---
--- Name: COLUMN exchange_sign_keys.exchange_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.exchange_sign_keys.exchange_pub IS 'Public online signing key of the exchange.';
-
-
---
--- Name: COLUMN exchange_sign_keys.master_sig; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.exchange_sign_keys.master_sig IS 'Signature affirming the validity of the signing key of purpose TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY.';
-
-
---
--- Name: COLUMN exchange_sign_keys.valid_from; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.exchange_sign_keys.valid_from IS 'Time when this online signing key will first be used to sign messages.';
-
-
---
--- Name: COLUMN exchange_sign_keys.expire_sign; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.exchange_sign_keys.expire_sign IS 'Time when this online signing key will no longer be used to sign.';
-
-
---
--- Name: COLUMN exchange_sign_keys.expire_legal; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.exchange_sign_keys.expire_legal IS 'Time when this online signing key legally expires.';
-
-
---
--- Name: exchange_sign_keys_esk_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.exchange_sign_keys_esk_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: exchange_sign_keys_esk_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.exchange_sign_keys_esk_serial_seq OWNED BY public.exchange_sign_keys.esk_serial;
-
-
---
--- Name: extension_details; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.extension_details (
- extension_details_serial_id bigint NOT NULL,
- extension_options character varying
-);
-
-
---
--- Name: TABLE extension_details; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.extension_details IS 'Extensions that were provided with deposits (not yet used).';
-
-
---
--- Name: COLUMN extension_details.extension_options; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.extension_details.extension_options IS 'JSON object with options set that the exchange needs to consider when executing a deposit. Supported details depend on the extensions supported by the exchange.';
-
-
---
--- Name: extension_details_extension_details_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.extension_details_extension_details_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: extension_details_extension_details_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.extension_details_extension_details_serial_id_seq OWNED BY public.extension_details.extension_details_serial_id;
-
-
---
--- Name: known_coins; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.known_coins (
- known_coin_id bigint NOT NULL,
- coin_pub bytea NOT NULL,
- age_hash bytea,
- denominations_serial bigint NOT NULL,
- denom_sig bytea NOT NULL,
- CONSTRAINT known_coins_age_hash_check CHECK ((length(age_hash) = 32)),
- 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: COLUMN known_coins.coin_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.known_coins.coin_pub IS 'EdDSA public key of the coin';
-
-
---
--- Name: COLUMN known_coins.age_hash; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.known_coins.age_hash IS 'Optional hash for age restrictions as per DD 24 (active if denom_type has the respective bit set)';
-
-
---
--- Name: COLUMN known_coins.denom_sig; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.known_coins.denom_sig IS '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.';
-
-
---
--- Name: known_coins_known_coin_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.known_coins_known_coin_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: known_coins_known_coin_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.known_coins_known_coin_id_seq OWNED BY public.known_coins.known_coin_id;
-
-
---
--- Name: merchant_accounts; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_accounts (
- account_serial bigint NOT NULL,
- merchant_serial bigint NOT NULL,
- h_wire bytea NOT NULL,
- salt bytea NOT NULL,
- payto_uri character varying NOT NULL,
- active boolean NOT NULL,
- CONSTRAINT merchant_accounts_h_wire_check CHECK ((length(h_wire) = 64)),
- CONSTRAINT merchant_accounts_salt_check CHECK ((length(salt) = 16))
-);
-
-
---
--- Name: TABLE merchant_accounts; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_accounts IS 'bank accounts of the instances';
-
-
---
--- Name: COLUMN merchant_accounts.h_wire; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_accounts.h_wire IS 'salted hash of payto_uri';
-
-
---
--- Name: COLUMN merchant_accounts.salt; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_accounts.salt IS 'salt used when hashing payto_uri into h_wire';
-
-
---
--- Name: COLUMN merchant_accounts.payto_uri; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_accounts.payto_uri IS 'payto URI of a merchant bank account';
-
-
---
--- Name: COLUMN merchant_accounts.active; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_accounts.active IS 'true if we actively use this bank account, false if it is just kept around for older contracts to refer to';
-
-
---
--- Name: merchant_accounts_account_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_accounts_account_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_accounts_account_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_accounts_account_serial_seq OWNED BY public.merchant_accounts.account_serial;
-
-
---
--- Name: merchant_contract_terms; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_contract_terms (
- order_serial bigint NOT NULL,
- merchant_serial bigint NOT NULL,
- order_id character varying NOT NULL,
- contract_terms bytea NOT NULL,
- h_contract_terms bytea NOT NULL,
- creation_time bigint NOT NULL,
- pay_deadline bigint NOT NULL,
- refund_deadline bigint NOT NULL,
- paid boolean DEFAULT false NOT NULL,
- wired boolean DEFAULT false NOT NULL,
- fulfillment_url character varying,
- session_id character varying DEFAULT ''::character varying NOT NULL,
- claim_token bytea NOT NULL,
- CONSTRAINT merchant_contract_terms_claim_token_check CHECK ((length(claim_token) = 16)),
- CONSTRAINT merchant_contract_terms_h_contract_terms_check CHECK ((length(h_contract_terms) = 64))
-);
-
-
---
--- Name: TABLE merchant_contract_terms; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_contract_terms IS 'Contracts are orders that have been claimed by a wallet';
-
-
---
--- Name: COLUMN merchant_contract_terms.merchant_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.merchant_serial IS 'Identifies the instance offering the contract';
-
-
---
--- Name: COLUMN merchant_contract_terms.order_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.order_id IS 'Not a foreign key into merchant_orders because paid contracts persist after expiration';
-
-
---
--- Name: COLUMN merchant_contract_terms.contract_terms; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.contract_terms IS 'These contract terms include the wallet nonce';
-
-
---
--- Name: COLUMN merchant_contract_terms.h_contract_terms; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.h_contract_terms IS 'Hash over contract_terms';
-
-
---
--- Name: COLUMN merchant_contract_terms.pay_deadline; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.pay_deadline IS 'How long is the offer valid. After this time, the order can be garbage collected';
-
-
---
--- Name: COLUMN merchant_contract_terms.refund_deadline; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.refund_deadline IS 'By what times do refunds have to be approved (useful to reject refund requests)';
-
-
---
--- Name: COLUMN merchant_contract_terms.paid; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.paid IS 'true implies the customer paid for this contract; order should be DELETEd from merchant_orders once paid is set to release merchant_order_locks; paid remains true even if the payment was later refunded';
-
-
---
--- Name: COLUMN merchant_contract_terms.wired; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.wired IS 'true implies the exchange wired us the full amount for all non-refunded payments under this contract';
-
-
---
--- Name: COLUMN merchant_contract_terms.fulfillment_url; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.fulfillment_url IS 'also included in contract_terms, but we need it here to SELECT on it during repurchase detection; can be NULL if the contract has no fulfillment URL';
-
-
---
--- Name: COLUMN merchant_contract_terms.session_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.session_id IS 'last session_id from we confirmed the paying client to use, empty string for none';
-
-
---
--- Name: COLUMN merchant_contract_terms.claim_token; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_contract_terms.claim_token IS 'Token optionally used to access the status of the order. All zeros (not NULL) if not used';
-
-
---
--- Name: merchant_deposit_to_transfer; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_deposit_to_transfer (
- deposit_serial bigint NOT NULL,
- coin_contribution_value_val bigint NOT NULL,
- coin_contribution_value_frac integer NOT NULL,
- credit_serial bigint NOT NULL,
- execution_time bigint NOT NULL,
- signkey_serial bigint NOT NULL,
- exchange_sig bytea NOT NULL,
- CONSTRAINT merchant_deposit_to_transfer_exchange_sig_check CHECK ((length(exchange_sig) = 64))
-);
-
-
---
--- Name: TABLE merchant_deposit_to_transfer; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_deposit_to_transfer IS 'Mapping of deposits to (possibly unconfirmed) wire transfers; NOTE: not used yet';
-
-
---
--- Name: COLUMN merchant_deposit_to_transfer.execution_time; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_deposit_to_transfer.execution_time IS 'Execution time as claimed by the exchange, roughly matches time seen by merchant';
-
-
---
--- Name: merchant_deposits; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_deposits (
- deposit_serial bigint NOT NULL,
- order_serial bigint,
- deposit_timestamp bigint 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_serial bigint NOT NULL,
- exchange_sig bytea NOT NULL,
- account_serial bigint NOT NULL,
- CONSTRAINT merchant_deposits_coin_pub_check CHECK ((length(coin_pub) = 32)),
- CONSTRAINT merchant_deposits_exchange_sig_check CHECK ((length(exchange_sig) = 64))
-);
-
-
---
--- Name: TABLE merchant_deposits; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_deposits IS 'Refunds approved by the merchant (backoffice) logic, excludes abort refunds';
-
-
---
--- Name: COLUMN merchant_deposits.deposit_timestamp; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_deposits.deposit_timestamp IS 'Time when the exchange generated the deposit confirmation';
-
-
---
--- Name: COLUMN merchant_deposits.wire_fee_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_deposits.wire_fee_val IS 'We MAY want to see if we should try to get this via merchant_exchange_wire_fees (not sure, may be too complicated with the date range, etc.)';
-
-
---
--- Name: COLUMN merchant_deposits.signkey_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_deposits.signkey_serial IS 'Online signing key of the exchange on the deposit confirmation';
-
-
---
--- Name: COLUMN merchant_deposits.exchange_sig; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_deposits.exchange_sig IS 'Signature of the exchange over the deposit confirmation';
-
-
---
--- Name: merchant_deposits_deposit_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_deposits_deposit_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_deposits_deposit_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_deposits_deposit_serial_seq OWNED BY public.merchant_deposits.deposit_serial;
-
-
---
--- Name: merchant_exchange_signing_keys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_exchange_signing_keys (
- signkey_serial bigint NOT NULL,
- master_pub bytea NOT NULL,
- exchange_pub bytea NOT NULL,
- start_date bigint NOT NULL,
- expire_date bigint NOT NULL,
- end_date bigint NOT NULL,
- master_sig bytea NOT NULL,
- CONSTRAINT merchant_exchange_signing_keys_exchange_pub_check CHECK ((length(exchange_pub) = 32)),
- CONSTRAINT merchant_exchange_signing_keys_master_pub_check CHECK ((length(master_pub) = 32)),
- CONSTRAINT merchant_exchange_signing_keys_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE merchant_exchange_signing_keys; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_exchange_signing_keys IS 'Here we store proofs of the exchange online signing keys being signed by the exchange master key';
-
-
---
--- Name: COLUMN merchant_exchange_signing_keys.master_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_exchange_signing_keys.master_pub IS 'Master public key of the exchange with these online signing keys';
-
-
---
--- Name: merchant_exchange_signing_keys_signkey_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_exchange_signing_keys_signkey_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_exchange_signing_keys_signkey_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_exchange_signing_keys_signkey_serial_seq OWNED BY public.merchant_exchange_signing_keys.signkey_serial;
-
-
---
--- Name: merchant_exchange_wire_fees; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_exchange_wire_fees (
- wirefee_serial bigint NOT NULL,
- master_pub bytea NOT NULL,
- h_wire_method bytea 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 merchant_exchange_wire_fees_h_wire_method_check CHECK ((length(h_wire_method) = 64)),
- CONSTRAINT merchant_exchange_wire_fees_master_pub_check CHECK ((length(master_pub) = 32)),
- CONSTRAINT merchant_exchange_wire_fees_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE merchant_exchange_wire_fees; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_exchange_wire_fees IS 'Here we store proofs of the wire fee structure of the various exchanges';
-
-
---
--- Name: COLUMN merchant_exchange_wire_fees.master_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_exchange_wire_fees.master_pub IS 'Master public key of the exchange with these wire fees';
-
-
---
--- Name: merchant_exchange_wire_fees_wirefee_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_exchange_wire_fees_wirefee_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_exchange_wire_fees_wirefee_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_exchange_wire_fees_wirefee_serial_seq OWNED BY public.merchant_exchange_wire_fees.wirefee_serial;
-
-
---
--- Name: merchant_instances; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_instances (
- merchant_serial bigint NOT NULL,
- merchant_pub bytea NOT NULL,
- merchant_id character varying NOT NULL,
- merchant_name character varying NOT NULL,
- address bytea NOT NULL,
- jurisdiction bytea NOT NULL,
- default_max_deposit_fee_val bigint NOT NULL,
- default_max_deposit_fee_frac integer NOT NULL,
- default_max_wire_fee_val bigint NOT NULL,
- default_max_wire_fee_frac integer NOT NULL,
- default_wire_fee_amortization integer NOT NULL,
- default_wire_transfer_delay bigint NOT NULL,
- default_pay_delay bigint NOT NULL,
- auth_hash bytea,
- auth_salt bytea,
- CONSTRAINT merchant_instances_auth_hash_check CHECK ((length(auth_hash) = 64)),
- CONSTRAINT merchant_instances_auth_salt_check CHECK ((length(auth_salt) = 32)),
- CONSTRAINT merchant_instances_merchant_pub_check CHECK ((length(merchant_pub) = 32))
-);
-
-
---
--- Name: TABLE merchant_instances; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_instances IS 'all the instances supported by this backend';
-
-
---
--- Name: COLUMN merchant_instances.merchant_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_instances.merchant_id IS 'identifier of the merchant as used in the base URL (required)';
-
-
---
--- Name: COLUMN merchant_instances.merchant_name; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_instances.merchant_name IS 'legal name of the merchant as a simple string (required)';
-
-
---
--- Name: COLUMN merchant_instances.address; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_instances.address IS 'physical address of the merchant as a Location in JSON format (required)';
-
-
---
--- Name: COLUMN merchant_instances.jurisdiction; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_instances.jurisdiction IS 'jurisdiction of the merchant as a Location in JSON format (required)';
-
-
---
--- Name: COLUMN merchant_instances.auth_hash; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_instances.auth_hash IS 'hash used for merchant back office Authorization, NULL for no check';
-
-
---
--- Name: COLUMN merchant_instances.auth_salt; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_instances.auth_salt IS 'salt to use when hashing Authorization header before comparing with auth_hash';
-
-
---
--- Name: merchant_instances_merchant_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_instances_merchant_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_instances_merchant_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_instances_merchant_serial_seq OWNED BY public.merchant_instances.merchant_serial;
-
-
---
--- Name: merchant_inventory; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_inventory (
- product_serial bigint NOT NULL,
- merchant_serial bigint NOT NULL,
- product_id character varying NOT NULL,
- description character varying NOT NULL,
- description_i18n bytea NOT NULL,
- unit character varying NOT NULL,
- image bytea NOT NULL,
- taxes bytea NOT NULL,
- price_val bigint NOT NULL,
- price_frac integer NOT NULL,
- total_stock bigint NOT NULL,
- total_sold bigint DEFAULT 0 NOT NULL,
- total_lost bigint DEFAULT 0 NOT NULL,
- address bytea NOT NULL,
- next_restock bigint NOT NULL
-);
-
-
---
--- Name: TABLE merchant_inventory; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_inventory IS 'products offered by the merchant (may be incomplete, frontend can override)';
-
-
---
--- Name: COLUMN merchant_inventory.description; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.description IS 'Human-readable product description';
-
-
---
--- Name: COLUMN merchant_inventory.description_i18n; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.description_i18n IS 'JSON map from IETF BCP 47 language tags to localized descriptions';
-
-
---
--- Name: COLUMN merchant_inventory.unit; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.unit IS 'Unit of sale for the product (liters, kilograms, packages)';
-
-
---
--- Name: COLUMN merchant_inventory.image; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.image IS 'NOT NULL, but can be 0 bytes; must contain an ImageDataUrl';
-
-
---
--- Name: COLUMN merchant_inventory.taxes; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.taxes IS 'JSON array containing taxes the merchant pays, must be JSON, but can be just "[]"';
-
-
---
--- Name: COLUMN merchant_inventory.price_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.price_val IS 'Current price of one unit of the product';
-
-
---
--- Name: COLUMN merchant_inventory.total_stock; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.total_stock IS 'A value of -1 is used for unlimited (electronic good), may never be lowered';
-
-
---
--- Name: COLUMN merchant_inventory.total_sold; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.total_sold IS 'Number of products sold, must be below total_stock, non-negative, may never be lowered';
-
-
---
--- Name: COLUMN merchant_inventory.total_lost; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.total_lost IS 'Number of products that used to be in stock but were lost (spoiled, damaged), may never be lowered; total_stock >= total_sold + total_lost must always hold';
-
-
---
--- Name: COLUMN merchant_inventory.address; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.address IS 'JSON formatted Location of where the product is stocked';
-
-
---
--- Name: COLUMN merchant_inventory.next_restock; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory.next_restock IS 'GNUnet absolute time indicating when the next restock is expected. 0 for unknown.';
-
-
---
--- Name: merchant_inventory_locks; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_inventory_locks (
- product_serial bigint NOT NULL,
- lock_uuid bytea NOT NULL,
- total_locked bigint NOT NULL,
- expiration bigint NOT NULL,
- CONSTRAINT merchant_inventory_locks_lock_uuid_check CHECK ((length(lock_uuid) = 16))
-);
-
-
---
--- Name: TABLE merchant_inventory_locks; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_inventory_locks IS 'locks on inventory helt by shopping carts; note that locks MAY not be honored if merchants increase total_lost for inventory';
-
-
---
--- Name: COLUMN merchant_inventory_locks.total_locked; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory_locks.total_locked IS 'how many units of the product does this lock reserve';
-
-
---
--- Name: COLUMN merchant_inventory_locks.expiration; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_inventory_locks.expiration IS 'when does this lock automatically expire (if no order is created)';
-
-
---
--- Name: merchant_inventory_product_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_inventory_product_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_inventory_product_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_inventory_product_serial_seq OWNED BY public.merchant_inventory.product_serial;
-
-
---
--- Name: merchant_keys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_keys (
- merchant_priv bytea NOT NULL,
- merchant_serial bigint NOT NULL,
- CONSTRAINT merchant_keys_merchant_priv_check CHECK ((length(merchant_priv) = 32))
-);
-
-
---
--- Name: TABLE merchant_keys; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_keys IS 'private keys of instances that have not been deleted';
-
-
---
--- Name: merchant_kyc; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_kyc (
- kyc_serial_id bigint NOT NULL,
- kyc_timestamp bigint NOT NULL,
- kyc_ok boolean DEFAULT false NOT NULL,
- exchange_sig bytea,
- exchange_pub bytea,
- exchange_kyc_serial bigint DEFAULT 0 NOT NULL,
- account_serial bigint NOT NULL,
- exchange_url character varying NOT NULL,
- CONSTRAINT merchant_kyc_exchange_pub_check CHECK ((length(exchange_pub) = 32)),
- CONSTRAINT merchant_kyc_exchange_sig_check CHECK ((length(exchange_sig) = 64))
-);
-
-
---
--- Name: TABLE merchant_kyc; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_kyc IS 'Status of the KYC process of a merchant account at an exchange';
-
-
---
--- Name: COLUMN merchant_kyc.kyc_timestamp; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_kyc.kyc_timestamp IS 'Last time we checked our KYC status at the exchange. Useful to re-check if the status is very stale. Also the timestamp used for the exchange signature (if present).';
-
-
---
--- Name: COLUMN merchant_kyc.kyc_ok; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_kyc.kyc_ok IS 'true if the KYC check was passed successfully';
-
-
---
--- Name: COLUMN merchant_kyc.exchange_sig; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_kyc.exchange_sig IS 'signature of the exchange affirming the KYC passed (or NULL if exchange does not require KYC or not kyc_ok)';
-
-
---
--- Name: COLUMN merchant_kyc.exchange_pub; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_kyc.exchange_pub IS 'public key used with exchange_sig (or NULL if exchange_sig is NULL)';
-
-
---
--- Name: COLUMN merchant_kyc.exchange_kyc_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_kyc.exchange_kyc_serial IS 'Number to use in the KYC-endpoints of the exchange to check the KYC status or begin the KYC process. 0 if we do not know it yet.';
-
-
---
--- Name: COLUMN merchant_kyc.account_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_kyc.account_serial IS 'Which bank account of the merchant is the KYC status for';
-
-
---
--- Name: COLUMN merchant_kyc.exchange_url; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_kyc.exchange_url IS 'Which exchange base URL is this KYC status valid for';
-
-
---
--- Name: merchant_kyc_kyc_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_kyc_kyc_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_kyc_kyc_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_kyc_kyc_serial_id_seq OWNED BY public.merchant_kyc.kyc_serial_id;
-
-
---
--- Name: merchant_order_locks; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_order_locks (
- product_serial bigint NOT NULL,
- total_locked bigint NOT NULL,
- order_serial bigint NOT NULL
-);
-
-
---
--- Name: TABLE merchant_order_locks; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_order_locks IS 'locks on orders awaiting claim and payment; note that locks MAY not be honored if merchants increase total_lost for inventory';
-
-
---
--- Name: COLUMN merchant_order_locks.total_locked; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_order_locks.total_locked IS 'how many units of the product does this lock reserve';
-
-
---
--- Name: merchant_orders; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_orders (
- order_serial bigint NOT NULL,
- merchant_serial bigint NOT NULL,
- order_id character varying NOT NULL,
- claim_token bytea NOT NULL,
- h_post_data bytea NOT NULL,
- pay_deadline bigint NOT NULL,
- creation_time bigint NOT NULL,
- contract_terms bytea NOT NULL,
- CONSTRAINT merchant_orders_claim_token_check CHECK ((length(claim_token) = 16)),
- CONSTRAINT merchant_orders_h_post_data_check CHECK ((length(h_post_data) = 64))
-);
-
-
---
--- Name: TABLE merchant_orders; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_orders IS 'Orders we offered to a customer, but that have not yet been claimed';
-
-
---
--- Name: COLUMN merchant_orders.merchant_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_orders.merchant_serial IS 'Identifies the instance offering the contract';
-
-
---
--- Name: COLUMN merchant_orders.claim_token; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_orders.claim_token IS 'Token optionally used to authorize the wallet to claim the order. All zeros (not NULL) if not used';
-
-
---
--- Name: COLUMN merchant_orders.h_post_data; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_orders.h_post_data IS 'Hash of the POST request that created this order, for idempotency checks';
-
-
---
--- Name: COLUMN merchant_orders.pay_deadline; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_orders.pay_deadline IS 'How long is the offer valid. After this time, the order can be garbage collected';
-
-
---
--- Name: COLUMN merchant_orders.contract_terms; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_orders.contract_terms IS 'Claiming changes the contract_terms, hence we have no hash of the terms in this table';
-
-
---
--- Name: merchant_orders_order_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_orders_order_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_orders_order_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_orders_order_serial_seq OWNED BY public.merchant_orders.order_serial;
-
-
---
--- Name: merchant_refund_proofs; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_refund_proofs (
- refund_serial bigint NOT NULL,
- exchange_sig bytea NOT NULL,
- signkey_serial bigint NOT NULL,
- CONSTRAINT merchant_refund_proofs_exchange_sig_check CHECK ((length(exchange_sig) = 64))
-);
-
-
---
--- Name: TABLE merchant_refund_proofs; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_refund_proofs IS 'Refunds confirmed by the exchange (not all approved refunds are grabbed by the wallet)';
-
-
---
--- Name: merchant_refunds; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_refunds (
- refund_serial bigint NOT NULL,
- order_serial bigint NOT NULL,
- rtransaction_id bigint NOT NULL,
- refund_timestamp bigint NOT NULL,
- coin_pub bytea NOT NULL,
- reason character varying NOT NULL,
- refund_amount_val bigint NOT NULL,
- refund_amount_frac integer NOT NULL
-);
-
-
---
--- Name: COLUMN merchant_refunds.rtransaction_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_refunds.rtransaction_id IS 'Needed for uniqueness in case a refund is increased for the same order';
-
-
---
--- Name: COLUMN merchant_refunds.refund_timestamp; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_refunds.refund_timestamp IS 'Needed for grouping of refunds in the wallet UI; has no semantics in the protocol (only for UX), but should be from the time when the merchant internally approved the refund';
-
-
---
--- Name: merchant_refunds_refund_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_refunds_refund_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_refunds_refund_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_refunds_refund_serial_seq OWNED BY public.merchant_refunds.refund_serial;
-
-
---
--- Name: merchant_tip_pickup_signatures; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tip_pickup_signatures (
- pickup_serial bigint NOT NULL,
- coin_offset integer NOT NULL,
- blind_sig bytea NOT NULL
-);
-
-
---
--- Name: TABLE merchant_tip_pickup_signatures; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_tip_pickup_signatures IS 'blind signatures we got from the exchange during the tip pickup';
-
-
---
--- Name: merchant_tip_pickups; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tip_pickups (
- pickup_serial bigint NOT NULL,
- tip_serial bigint 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: TABLE merchant_tip_pickups; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_tip_pickups IS 'tips that have been picked up';
-
-
---
--- Name: merchant_tip_pickups_pickup_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_tip_pickups_pickup_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_tip_pickups_pickup_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_tip_pickups_pickup_serial_seq OWNED BY public.merchant_tip_pickups.pickup_serial;
-
-
---
--- Name: merchant_tip_reserve_keys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tip_reserve_keys (
- reserve_serial bigint NOT NULL,
- reserve_priv bytea NOT NULL,
- exchange_url character varying NOT NULL,
- payto_uri character varying,
- CONSTRAINT merchant_tip_reserve_keys_reserve_priv_check CHECK ((length(reserve_priv) = 32))
-);
-
-
---
--- Name: COLUMN merchant_tip_reserve_keys.payto_uri; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tip_reserve_keys.payto_uri IS 'payto:// URI used to fund the reserve, may be NULL once reserve is funded';
-
-
---
--- Name: merchant_tip_reserves; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tip_reserves (
- reserve_serial bigint NOT NULL,
- reserve_pub bytea NOT NULL,
- merchant_serial bigint NOT NULL,
- creation_time bigint NOT NULL,
- expiration bigint NOT NULL,
- merchant_initial_balance_val bigint NOT NULL,
- merchant_initial_balance_frac integer NOT NULL,
- exchange_initial_balance_val bigint DEFAULT 0 NOT NULL,
- exchange_initial_balance_frac integer DEFAULT 0 NOT NULL,
- tips_committed_val bigint DEFAULT 0 NOT NULL,
- tips_committed_frac integer DEFAULT 0 NOT NULL,
- tips_picked_up_val bigint DEFAULT 0 NOT NULL,
- tips_picked_up_frac integer DEFAULT 0 NOT NULL,
- CONSTRAINT merchant_tip_reserves_reserve_pub_check CHECK ((length(reserve_pub) = 32))
-);
-
-
---
--- Name: TABLE merchant_tip_reserves; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_tip_reserves IS 'private keys of reserves that have not been deleted';
-
-
---
--- Name: COLUMN merchant_tip_reserves.expiration; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tip_reserves.expiration IS 'FIXME: EXCHANGE API needs to tell us when reserves close if we are to compute this';
-
-
---
--- Name: COLUMN merchant_tip_reserves.merchant_initial_balance_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tip_reserves.merchant_initial_balance_val IS 'Set to the initial balance the merchant told us when creating the reserve';
-
-
---
--- Name: COLUMN merchant_tip_reserves.exchange_initial_balance_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tip_reserves.exchange_initial_balance_val IS 'Set to the initial balance the exchange told us when we queried the reserve status';
-
-
---
--- Name: COLUMN merchant_tip_reserves.tips_committed_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tip_reserves.tips_committed_val IS 'Amount of outstanding approved tips that have not been picked up';
-
-
---
--- Name: COLUMN merchant_tip_reserves.tips_picked_up_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tip_reserves.tips_picked_up_val IS 'Total amount tips that have been picked up from this reserve';
-
-
---
--- Name: merchant_tip_reserves_reserve_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_tip_reserves_reserve_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_tip_reserves_reserve_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_tip_reserves_reserve_serial_seq OWNED BY public.merchant_tip_reserves.reserve_serial;
-
-
---
--- Name: merchant_tips; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_tips (
- tip_serial bigint NOT NULL,
- reserve_serial bigint NOT NULL,
- tip_id bytea NOT NULL,
- justification character varying NOT NULL,
- next_url character varying NOT NULL,
- expiration bigint NOT NULL,
- amount_val bigint NOT NULL,
- amount_frac integer NOT NULL,
- picked_up_val bigint DEFAULT 0 NOT NULL,
- picked_up_frac integer DEFAULT 0 NOT NULL,
- was_picked_up boolean DEFAULT false NOT NULL,
- CONSTRAINT merchant_tips_tip_id_check CHECK ((length(tip_id) = 64))
-);
-
-
---
--- Name: TABLE merchant_tips; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_tips IS 'tips that have been authorized';
-
-
---
--- Name: COLUMN merchant_tips.reserve_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tips.reserve_serial IS 'Reserve from which this tip is funded';
-
-
---
--- Name: COLUMN merchant_tips.expiration; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tips.expiration IS 'by when does the client have to pick up the tip';
-
-
---
--- Name: COLUMN merchant_tips.amount_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tips.amount_val IS 'total transaction cost for all coins including withdraw fees';
-
-
---
--- Name: COLUMN merchant_tips.picked_up_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_tips.picked_up_val IS 'Tip amount left to be picked up';
-
-
---
--- Name: merchant_tips_tip_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_tips_tip_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_tips_tip_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_tips_tip_serial_seq OWNED BY public.merchant_tips.tip_serial;
-
-
---
--- Name: merchant_transfer_signatures; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_transfer_signatures (
- credit_serial bigint NOT NULL,
- signkey_serial bigint NOT NULL,
- wire_fee_val bigint NOT NULL,
- wire_fee_frac integer NOT NULL,
- execution_time bigint NOT NULL,
- exchange_sig bytea NOT NULL,
- credit_amount_val bigint NOT NULL,
- credit_amount_frac integer NOT NULL,
- CONSTRAINT merchant_transfer_signatures_exchange_sig_check CHECK ((length(exchange_sig) = 64))
-);
-
-
---
--- Name: TABLE merchant_transfer_signatures; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_transfer_signatures IS 'table represents the main information returned from the /transfer request to the exchange.';
-
-
---
--- Name: COLUMN merchant_transfer_signatures.execution_time; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_transfer_signatures.execution_time IS 'Execution time as claimed by the exchange, roughly matches time seen by merchant';
-
-
---
--- Name: merchant_transfer_to_coin; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_transfer_to_coin (
- deposit_serial bigint NOT NULL,
- credit_serial bigint NOT NULL,
- offset_in_exchange_list bigint NOT NULL,
- exchange_deposit_value_val bigint NOT NULL,
- exchange_deposit_value_frac integer NOT NULL,
- exchange_deposit_fee_val bigint NOT NULL,
- exchange_deposit_fee_frac integer NOT NULL
-);
-
-
---
--- Name: TABLE merchant_transfer_to_coin; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_transfer_to_coin IS 'Mapping of (credit) transfers to (deposited) coins';
-
-
---
--- Name: COLUMN merchant_transfer_to_coin.exchange_deposit_value_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_transfer_to_coin.exchange_deposit_value_val IS 'Deposit value as claimed by the exchange, should match our values in merchant_deposits minus refunds';
-
-
---
--- Name: COLUMN merchant_transfer_to_coin.exchange_deposit_fee_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_transfer_to_coin.exchange_deposit_fee_val IS 'Deposit value as claimed by the exchange, should match our values in merchant_deposits';
-
-
---
--- Name: merchant_transfers; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.merchant_transfers (
- credit_serial bigint NOT NULL,
- exchange_url character varying NOT NULL,
- wtid bytea,
- credit_amount_val bigint NOT NULL,
- credit_amount_frac integer NOT NULL,
- account_serial bigint NOT NULL,
- verified boolean DEFAULT false NOT NULL,
- confirmed boolean DEFAULT false NOT NULL,
- CONSTRAINT merchant_transfers_wtid_check CHECK ((length(wtid) = 32))
-);
-
-
---
--- Name: TABLE merchant_transfers; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.merchant_transfers IS 'table represents the information provided by the (trusted) merchant about incoming wire transfers';
-
-
---
--- Name: COLUMN merchant_transfers.credit_amount_val; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_transfers.credit_amount_val IS 'actual value of the (aggregated) wire transfer, excluding the wire fee, according to the exchange';
-
-
---
--- Name: COLUMN merchant_transfers.verified; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_transfers.verified IS 'true once we got an acceptable response from the exchange for this transfer';
-
-
---
--- Name: COLUMN merchant_transfers.confirmed; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.merchant_transfers.confirmed IS 'true once the merchant confirmed that this transfer was received';
-
-
---
--- Name: merchant_transfers_credit_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.merchant_transfers_credit_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: merchant_transfers_credit_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.merchant_transfers_credit_serial_seq OWNED BY public.merchant_transfers.credit_serial;
-
-
---
--- 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,
- failed 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: COLUMN prewire.finished; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.prewire.finished IS 'set to TRUE once bank confirmed receiving the wire transfer request';
-
-
---
--- Name: COLUMN prewire.failed; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.prewire.failed IS 'set to TRUE if the bank responded with a non-transient failure to our transfer request';
-
-
---
--- Name: COLUMN prewire.buf; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.prewire.buf IS 'serialized data to send to the bank to execute the wire transfer';
-
-
---
--- 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,
- known_coin_id bigint 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,
- reserve_out_serial_id bigint 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.known_coin_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.recoup.known_coin_id IS 'Do not CASCADE ON DROP on the known_coin_id, as we may keep the coin alive!';
-
-
---
--- Name: COLUMN recoup.reserve_out_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.recoup.reserve_out_serial_id IS 'Identifies the h_blind_ev of the recouped coin.';
-
-
---
--- 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,
- known_coin_id bigint 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,
- rrc_serial bigint 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.known_coin_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.recoup_refresh.known_coin_id IS 'Do not CASCADE ON DROP on the known_coin_id, as we may keep the coin alive!';
-
-
---
--- Name: COLUMN recoup_refresh.rrc_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.recoup_refresh.rrc_serial IS 'Identifies the h_blind_ev of the recouped coin (as h_coin_ev).';
-
-
---
--- 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_known_coin_id bigint 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 (
- rrc_serial bigint NOT NULL,
- melt_serial_id bigint NOT NULL,
- freshcoin_index integer NOT NULL,
- link_sig bytea NOT NULL,
- denominations_serial bigint 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.rrc_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_revealed_coins.rrc_serial IS 'needed for exchange-auditor replication logic';
-
-
---
--- Name: COLUMN refresh_revealed_coins.melt_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_revealed_coins.melt_serial_id IS 'Identifies the refresh commitment (rc) of 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_revealed_coins_rrc_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.refresh_revealed_coins_rrc_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: refresh_revealed_coins_rrc_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.refresh_revealed_coins_rrc_serial_seq OWNED BY public.refresh_revealed_coins.rrc_serial;
-
-
---
--- Name: refresh_transfer_keys; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.refresh_transfer_keys (
- rtc_serial bigint NOT NULL,
- melt_serial_id bigint 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.rtc_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_transfer_keys.rtc_serial IS 'needed for exchange-auditor replication logic';
-
-
---
--- Name: COLUMN refresh_transfer_keys.melt_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refresh_transfer_keys.melt_serial_id IS 'Identifies the refresh commitment (rc) of the 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: refresh_transfer_keys_rtc_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.refresh_transfer_keys_rtc_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: refresh_transfer_keys_rtc_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.refresh_transfer_keys_rtc_serial_seq OWNED BY public.refresh_transfer_keys.rtc_serial;
-
-
---
--- Name: refunds; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.refunds (
- refund_serial_id bigint NOT NULL,
- deposit_serial_id bigint NOT NULL,
- merchant_sig bytea NOT NULL,
- rtransaction_id bigint NOT NULL,
- amount_with_fee_val bigint NOT NULL,
- amount_with_fee_frac integer NOT NULL,
- 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.deposit_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.refunds.deposit_serial_id IS 'Identifies ONLY the merchant_pub, h_contract_terms and known_coin_id. Multiple deposits may match a refund, this only identifies one of them.';
-
-
---
--- 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_uuid bigint NOT NULL,
- reserve_pub bytea 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_uuid bigint NOT NULL,
- execution_date bigint NOT NULL,
- wtid bytea NOT NULL,
- wire_target_serial_id bigint 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: COLUMN reserves_close.wire_target_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.reserves_close.wire_target_serial_id IS 'Identifies the credited bank account (and KYC status). Note that closing does not depend on KYC.';
-
-
---
--- 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_uuid bigint NOT NULL,
- wire_reference bigint NOT NULL,
- credit_val bigint NOT NULL,
- credit_frac integer NOT NULL,
- wire_source_serial_id bigint 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: COLUMN reserves_in.wire_source_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.reserves_in.wire_source_serial_id IS 'Identifies the debited bank account and KYC status';
-
-
---
--- 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,
- denominations_serial bigint NOT NULL,
- denom_sig bytea NOT NULL,
- reserve_uuid bigint 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.denominations_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.reserves_out.denominations_serial 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: reserves_reserve_uuid_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.reserves_reserve_uuid_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: reserves_reserve_uuid_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.reserves_reserve_uuid_seq OWNED BY public.reserves.reserve_uuid;
-
-
---
--- Name: revolving_work_shards; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE UNLOGGED TABLE public.revolving_work_shards (
- shard_serial_id bigint NOT NULL,
- last_attempt bigint NOT NULL,
- start_row integer NOT NULL,
- end_row integer NOT NULL,
- active boolean DEFAULT false NOT NULL,
- job_name character varying NOT NULL
-);
-
-
---
--- Name: TABLE revolving_work_shards; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.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"';
-
-
---
--- Name: COLUMN revolving_work_shards.shard_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.revolving_work_shards.shard_serial_id IS 'unique serial number identifying the shard';
-
-
---
--- Name: COLUMN revolving_work_shards.last_attempt; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.revolving_work_shards.last_attempt IS 'last time a worker attempted to work on the shard';
-
-
---
--- Name: COLUMN revolving_work_shards.start_row; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.revolving_work_shards.start_row IS 'row at which the shard scope starts, inclusive';
-
-
---
--- Name: COLUMN revolving_work_shards.end_row; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.revolving_work_shards.end_row IS 'row at which the shard scope ends, exclusive';
-
-
---
--- Name: COLUMN revolving_work_shards.active; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.revolving_work_shards.active IS 'set to TRUE when a worker is active on the shard';
-
-
---
--- Name: COLUMN revolving_work_shards.job_name; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.revolving_work_shards.job_name IS 'unique name of the job the workers on this shard are performing';
-
-
---
--- Name: revolving_work_shards_shard_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.revolving_work_shards_shard_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: revolving_work_shards_shard_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.revolving_work_shards_shard_serial_id_seq OWNED BY public.revolving_work_shards.shard_serial_id;
-
-
---
--- Name: signkey_revocations; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.signkey_revocations (
- signkey_revocations_serial_id bigint NOT NULL,
- esk_serial bigint NOT NULL,
- master_sig bytea NOT NULL,
- CONSTRAINT signkey_revocations_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE signkey_revocations; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.signkey_revocations IS 'remembering which online signing keys have been revoked';
-
-
---
--- Name: signkey_revocations_signkey_revocations_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.signkey_revocations_signkey_revocations_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: signkey_revocations_signkey_revocations_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.signkey_revocations_signkey_revocations_serial_id_seq OWNED BY public.signkey_revocations.signkey_revocations_serial_id;
-
-
---
--- Name: wire_accounts; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.wire_accounts (
- payto_uri character varying NOT NULL,
- master_sig bytea,
- is_active boolean NOT NULL,
- last_change bigint NOT NULL,
- CONSTRAINT wire_accounts_master_sig_check CHECK ((length(master_sig) = 64))
-);
-
-
---
--- Name: TABLE wire_accounts; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.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.';
-
-
---
--- Name: COLUMN wire_accounts.payto_uri; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_accounts.payto_uri IS 'payto URI (RFC 8905) with the bank account of the exchange.';
-
-
---
--- Name: COLUMN wire_accounts.master_sig; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_accounts.master_sig IS 'Signature of purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS';
-
-
---
--- Name: COLUMN wire_accounts.is_active; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_accounts.is_active IS 'true if we are currently supporting the use of this account.';
-
-
---
--- Name: COLUMN wire_accounts.last_change; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_accounts.last_change IS 'Latest time when active status changed. Used to detect replays of old messages.';
-
-
---
--- 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 NOT NULL,
- wire_out_off bigint NOT NULL
-);
-
-
---
--- 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_fee_serial bigint NOT NULL,
- 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: COLUMN wire_fee.wire_fee_serial; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_fee.wire_fee_serial IS 'needed for exchange-auditor replication logic';
-
-
---
--- Name: wire_fee_wire_fee_serial_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.wire_fee_wire_fee_serial_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: wire_fee_wire_fee_serial_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.wire_fee_wire_fee_serial_seq OWNED BY public.wire_fee.wire_fee_serial;
-
-
---
--- 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_serial_id bigint 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: COLUMN wire_out.wire_target_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_out.wire_target_serial_id IS 'Identifies the credited bank account and KYC status';
-
-
---
--- Name: COLUMN wire_out.exchange_account_section; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_out.exchange_account_section IS 'identifies the configuration section with the debit account of this payment';
-
-
---
--- 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: wire_targets; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.wire_targets (
- wire_target_serial_id bigint NOT NULL,
- h_payto bytea NOT NULL,
- payto_uri character varying NOT NULL,
- kyc_ok boolean DEFAULT false NOT NULL,
- external_id character varying,
- CONSTRAINT wire_targets_h_payto_check CHECK ((length(h_payto) = 64))
-);
-
-
---
--- Name: TABLE wire_targets; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.wire_targets IS 'All recipients of money via the exchange';
-
-
---
--- Name: COLUMN wire_targets.h_payto; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_targets.h_payto IS 'Unsalted hash of payto_uri';
-
-
---
--- Name: COLUMN wire_targets.payto_uri; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_targets.payto_uri IS 'Can be a regular bank account, or also be a URI identifying a reserve-account (for P2P payments)';
-
-
---
--- Name: COLUMN wire_targets.kyc_ok; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_targets.kyc_ok IS 'true if the KYC check was passed successfully';
-
-
---
--- Name: COLUMN wire_targets.external_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.wire_targets.external_id IS 'Name of the user that was used for OAuth 2.0-based legitimization';
-
-
---
--- Name: wire_targets_wire_target_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.wire_targets_wire_target_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: wire_targets_wire_target_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.wire_targets_wire_target_serial_id_seq OWNED BY public.wire_targets.wire_target_serial_id;
-
-
---
--- Name: work_shards; Type: TABLE; Schema: public; Owner: -
---
-
-CREATE TABLE public.work_shards (
- shard_serial_id bigint NOT NULL,
- last_attempt bigint NOT NULL,
- start_row bigint NOT NULL,
- end_row bigint NOT NULL,
- completed boolean DEFAULT false NOT NULL,
- job_name character varying NOT NULL
-);
-
-
---
--- Name: TABLE work_shards; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON TABLE public.work_shards IS 'coordinates work between multiple processes working on the same job';
-
-
---
--- Name: COLUMN work_shards.shard_serial_id; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.work_shards.shard_serial_id IS 'unique serial number identifying the shard';
-
-
---
--- Name: COLUMN work_shards.last_attempt; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.work_shards.last_attempt IS 'last time a worker attempted to work on the shard';
-
-
---
--- Name: COLUMN work_shards.start_row; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.work_shards.start_row IS 'row at which the shard scope starts, inclusive';
-
-
---
--- Name: COLUMN work_shards.end_row; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.work_shards.end_row IS 'row at which the shard scope ends, exclusive';
-
-
---
--- Name: COLUMN work_shards.completed; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.work_shards.completed IS 'set to TRUE once the shard is finished by a worker';
-
-
---
--- Name: COLUMN work_shards.job_name; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON COLUMN public.work_shards.job_name IS 'unique name of the job the workers on this shard are performing';
-
-
---
--- Name: work_shards_shard_serial_id_seq; Type: SEQUENCE; Schema: public; Owner: -
---
-
-CREATE SEQUENCE public.work_shards_shard_serial_id_seq
- START WITH 1
- INCREMENT BY 1
- NO MINVALUE
- NO MAXVALUE
- CACHE 1;
-
-
---
--- Name: work_shards_shard_serial_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
---
-
-ALTER SEQUENCE public.work_shards_shard_serial_id_seq OWNED BY public.work_shards.shard_serial_id;
-
-
---
--- 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_denom_sigs auditor_denom_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denom_sigs ALTER COLUMN auditor_denom_serial SET DEFAULT nextval('public.auditor_denom_sigs_auditor_denom_serial_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: auditors auditor_uuid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditors ALTER COLUMN auditor_uuid SET DEFAULT nextval('public.auditors_auditor_uuid_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: denominations denominations_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denominations ALTER COLUMN denominations_serial SET DEFAULT nextval('public.denominations_denominations_serial_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: exchange_sign_keys esk_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.exchange_sign_keys ALTER COLUMN esk_serial SET DEFAULT nextval('public.exchange_sign_keys_esk_serial_seq'::regclass);
-
-
---
--- Name: extension_details extension_details_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.extension_details ALTER COLUMN extension_details_serial_id SET DEFAULT nextval('public.extension_details_extension_details_serial_id_seq'::regclass);
-
-
---
--- Name: known_coins known_coin_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.known_coins ALTER COLUMN known_coin_id SET DEFAULT nextval('public.known_coins_known_coin_id_seq'::regclass);
-
-
---
--- Name: merchant_accounts account_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_accounts ALTER COLUMN account_serial SET DEFAULT nextval('public.merchant_accounts_account_serial_seq'::regclass);
-
-
---
--- Name: merchant_deposits deposit_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits ALTER COLUMN deposit_serial SET DEFAULT nextval('public.merchant_deposits_deposit_serial_seq'::regclass);
-
-
---
--- Name: merchant_exchange_signing_keys signkey_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_exchange_signing_keys ALTER COLUMN signkey_serial SET DEFAULT nextval('public.merchant_exchange_signing_keys_signkey_serial_seq'::regclass);
-
-
---
--- Name: merchant_exchange_wire_fees wirefee_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_exchange_wire_fees ALTER COLUMN wirefee_serial SET DEFAULT nextval('public.merchant_exchange_wire_fees_wirefee_serial_seq'::regclass);
-
-
---
--- Name: merchant_instances merchant_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_instances ALTER COLUMN merchant_serial SET DEFAULT nextval('public.merchant_instances_merchant_serial_seq'::regclass);
-
-
---
--- Name: merchant_inventory product_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_inventory ALTER COLUMN product_serial SET DEFAULT nextval('public.merchant_inventory_product_serial_seq'::regclass);
-
-
---
--- Name: merchant_kyc kyc_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_kyc ALTER COLUMN kyc_serial_id SET DEFAULT nextval('public.merchant_kyc_kyc_serial_id_seq'::regclass);
-
-
---
--- Name: merchant_orders order_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_orders ALTER COLUMN order_serial SET DEFAULT nextval('public.merchant_orders_order_serial_seq'::regclass);
-
-
---
--- Name: merchant_refunds refund_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refunds ALTER COLUMN refund_serial SET DEFAULT nextval('public.merchant_refunds_refund_serial_seq'::regclass);
-
-
---
--- Name: merchant_tip_pickups pickup_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_pickups ALTER COLUMN pickup_serial SET DEFAULT nextval('public.merchant_tip_pickups_pickup_serial_seq'::regclass);
-
-
---
--- Name: merchant_tip_reserves reserve_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserves ALTER COLUMN reserve_serial SET DEFAULT nextval('public.merchant_tip_reserves_reserve_serial_seq'::regclass);
-
-
---
--- Name: merchant_tips tip_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tips ALTER COLUMN tip_serial SET DEFAULT nextval('public.merchant_tips_tip_serial_seq'::regclass);
-
-
---
--- Name: merchant_transfers credit_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfers ALTER COLUMN credit_serial SET DEFAULT nextval('public.merchant_transfers_credit_serial_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: refresh_revealed_coins rrc_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins ALTER COLUMN rrc_serial SET DEFAULT nextval('public.refresh_revealed_coins_rrc_serial_seq'::regclass);
-
-
---
--- Name: refresh_transfer_keys rtc_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_transfer_keys ALTER COLUMN rtc_serial SET DEFAULT nextval('public.refresh_transfer_keys_rtc_serial_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 reserve_uuid; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves ALTER COLUMN reserve_uuid SET DEFAULT nextval('public.reserves_reserve_uuid_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: revolving_work_shards shard_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.revolving_work_shards ALTER COLUMN shard_serial_id SET DEFAULT nextval('public.revolving_work_shards_shard_serial_id_seq'::regclass);
-
-
---
--- Name: signkey_revocations signkey_revocations_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.signkey_revocations ALTER COLUMN signkey_revocations_serial_id SET DEFAULT nextval('public.signkey_revocations_signkey_revocations_serial_id_seq'::regclass);
-
-
---
--- Name: wire_fee wire_fee_serial; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_fee ALTER COLUMN wire_fee_serial SET DEFAULT nextval('public.wire_fee_wire_fee_serial_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);
-
-
---
--- Name: wire_targets wire_target_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_targets ALTER COLUMN wire_target_serial_id SET DEFAULT nextval('public.wire_targets_wire_target_serial_id_seq'::regclass);
-
-
---
--- Name: work_shards shard_serial_id; Type: DEFAULT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.work_shards ALTER COLUMN shard_serial_id SET DEFAULT nextval('public.work_shards_shard_serial_id_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 2021-12-01 14:55:39.517187+01 dold {} {}
-merchant-0001 2021-12-01 14:55:40.300946+01 dold {} {}
-merchant-0002 2021-12-01 14:55:40.787261+01 dold {} {}
-merchant-0003 2021-12-01 14:55:40.809705+01 dold {} {}
-auditor-0001 2021-12-01 14:55:40.859325+01 dold {} {}
-\.
-
-
---
--- 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
-t 9 +TESTKUDOS:0 9
-f 10 +TESTKUDOS:0 10
-f 11 +TESTKUDOS:0 11
-t 1 -TESTKUDOS:100 1
-f 12 +TESTKUDOS:92 12
-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 2021-12-01 14:55:51.004131+01 f baf59f77-f9a5-4bd4-aa67-3f91cff15fb8 12 1
-2 TESTKUDOS:8 JKWQ51N493CNDSCS0KVXZVTNFE6XDS3WFQ095XMVR4VRS1ZCFZJ0 2021-12-01 14:55:55.047101+01 f f7fd8a6a-ec64-4d40-ac5a-2444c33211b2 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;
-3d015ae3-e8cf-4029-b564-379d3d2fa616 TESTKUDOS:8 t t f JKWQ51N493CNDSCS0KVXZVTNFE6XDS3WFQ095XMVR4VRS1ZCFZJ0 2 12
-\.
-
-
---
--- 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_denom_sigs; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_denom_sigs (auditor_denom_serial, auditor_uuid, denominations_serial, auditor_sig) FROM stdin;
-1 1 45 \\xf066a31fc812d18c7614205f5ddcfdb3ece1018d9432f5e818290c163a2051e735e53bdf757eab36c4122334583acb2a37690d35b7fe85144a4859eab3dbda0d
-2 1 269 \\x530aa936d055e56c6830cd6eb9f1fe48f5628693baf4e50337d7cc6ac760b9f657a9f478490a7587f6dff604efaf62c3480ce2aae88368a105b37e79f33fa801
-3 1 329 \\x4ce84fa7805310a60839f31bc2f347026f43415c85400ab95289b9dab53f9cd728c24f3a23c8b9ac8aa8bd2bb6414f96d7372c36e46c6f581445c9ec0e697407
-4 1 334 \\x3c82622e870205ff004ce0572bd065568e93e131e0c50ac9bc8e5edb4a5a51b7c58c587ab059876e76f6ad358254aeeab64e0ebb90df60352d9e0d42b2285a07
-5 1 376 \\x73eb54bd69c0f711c0569d650c6a51e5270fde1b4eeb48575ec85de59a7ed9bffbd31a61e0b54361b6f3566995763ed8ba01df0dec0f4cd46fb45e23b7e02906
-6 1 60 \\xe6d8fa46eecebb00771292bad3aca1263c70e19b61715a60f8f795f9830d8988bae04b09c9fde9b64bc3aa77410d73a37aa607c2bb056f98258c5b789573a70b
-7 1 122 \\xe5a627ba3d18279760019e06bc6ae7dae37eda5fa119dd748fb29e9e00ec6c36ce234e6cec79c15c841b4e759c03f0895528e738b2bfc0dc29bc35b1ad63ff0a
-8 1 318 \\x327ec0a036cd3af79c76ec72914c439c4cb3d1cad467cde15b4c0e588725b90c46c670cb39bc6978b5c6b8d835533101bbe37900c341d091319db1593d41fb00
-9 1 309 \\xfb97a4469cf8d6587c58d4221bf3457d36d1f10662940d6dce23ea6e22b6c5c986df8f9736e82a5a79ee9dbbdc631642dc1d2cc2121e1e0051b4d1ff39d19f07
-10 1 96 \\x5d7669aa9dfd18c7bc11972343391d540e20fed09ba346856e3c2a9a34072a06fd41c34c9a863b4a2d238851bbd7b23f24bf6f12a7663e5cd3a519708b669100
-11 1 281 \\x7376e463abc76c75c065c70f5804610aeaf7aa18168f7b9973806ee833e3bf605a8d20527f1d009a2b019c79aa431fe35045e924903c241cc2d9e3716142cf0b
-12 1 37 \\xe925f348bd493a9bf94affe27226eb99a768f6b35ad4349c65d875a4c8cb26b71ee04d2df86a66ba46d072056fba8d9547c3a77689f722f0902927a6fb7d8809
-13 1 164 \\xa00177126fca70c345c13b2daefea194e1670cec95a726de125daada01a73390011d4b4164cee5a8a243f47955ea961229c792a36ef4a477cd875cf714645b03
-14 1 109 \\xd862eb80353a50e2d19c6e5b402b515567466afaf3645273c197c6c7a00b2fe10d79a14939e5206652bd7867779602aa7a6cf75d3880d2039f9562573f124d09
-15 1 383 \\x8833eacfbdf16cda92e25b533356cbaca942d708164ed4ff0faa4bacc66e45f9e31b075a0bcac265dfaaf6f0b489e88de085018961b62518bcbcee1920e0990e
-16 1 114 \\x6dde51e6ed450a677b2b50a8e06c6f8363f8cc76dd1adcb6f5c05470e31eb1f3400e7268115a896e74d750b42ad105c96b64566a89fb75ebed16d129063aca03
-17 1 338 \\x891fc5f799f459db848c7db7efd85273914534225bc35086a38c5fcd9a3e73b817c4eda33acda2651b1a4b89dea0acc66ac2d59dfdee884b9e0e0f89723ba20e
-18 1 35 \\xcc78793012aa737b4e5bb370a6be8be27147b4c7681aaf1bef8cde491abe235a538ce17ec7fb8d9fac43711811abe1dab8954e31fba27f13a04ece157325a609
-19 1 44 \\x12642b17ed7b86c96679ebe8dae3afe4acff7be670808e222e7854218b984ece43d859305cea4814f58ae57b49eeef80c8ac9221d17063128af6cec0006e4402
-20 1 181 \\xca4eb28d8871034b23d6e8cf1a0c69e5445cefeee688e2cf39af944ecacae369a4c4b58b77d18c21053ff76c28ffa49d41c6a289861bee4cc10882c6181f740a
-21 1 391 \\x83881c68a3335bea3e0c26a6561b253a42815f33d3ca458429fe48d295cc0e9ffdd891d0ecaca37c8fe7ef5b0bbceedaaee7621c86765abcb49abc38eb753a05
-22 1 282 \\x2843dee1c4630540d80a651c9e040a0b2cb89e568bcbf491b9a4b5a772d8a6b1b6dc885579ab186a5301ae316ac3751fd195cf6e26d98aac811258892e1aa302
-23 1 343 \\xab579eaa72346e954f54ff93217210ccf50b6ec4dcd4989d3705767bb58b8a743ae16c06d3cbd99a28812c4632a538468c53aa1fad94c9dd153fe7ddb797230f
-24 1 214 \\xeef7c4cb9952eb0c634c56f86fcc802607d6b019d231aebbb97ed2656d263453cd09a5f52938b3e2c6b0054b86a65312c1070a4858f9609a602b270378c4e009
-25 1 235 \\xf46bfb3e2a1bf36ca5338a51e5f1e82b23fb52a31915b205a39a83964d1160cd37078aefd9d6de6e7c37a603d7b9ffdbccb9571f2fc28ea7f9330c4d390dad00
-26 1 371 \\x2c306c759df50d92be76bd79d885b32ceadd5513eb8da5504afe7b5f59f7fedb766b98ce7e20f5f32c39b35768ddf37cde9943e4dfacd28a93713196492ee10b
-27 1 397 \\x1b4b5ada16c28dd83d1904245b815e4908eb5d91ebeb36dbe159f99dc7645bce01301e8c62ace3ccad1160fff9849721832406d966d247f0e873c57d9a5ce60b
-28 1 265 \\x54a487a3f03e1fdd708bb59f16eb4a9e09c8fe4bb1913efbcaa7668dcd5c5a99d9a7d4b4f0d91543c840615b34fb8bc4d1cd4f82b5b8330d7c9b2a3662039101
-29 1 150 \\x2e77f5e90f40f8bc6e746edc943a85a8da4e1080918f4e0a982f9f37aff226ec8c90b1fed7c8d104e30b15e6b1336fded49d740864c2a6f723e955ec8dd2d404
-30 1 132 \\x175c59d3f78333c83d8f04218957d9aa9418bd7f170be0a69d167f123ae4a9dde958c5d6217c1f347982185a2814c06febe450f15ae78c0d408e143db65b0200
-31 1 243 \\x7f3853e4db5a7cfeff8bfc560e1be35b228292f85edd24524c5dfd993eaa53e5bb8d0a657b21daaaeffb2ef9bc925ca35d3043cee8103f764d44acc925d1b007
-32 1 420 \\x3e3d555d364df4b018340f45442b042c03716be64ede327ecc2613416b0f8410c919d18c80db1df558df7daa456384e8b9fa8f2dbe6b8bf0803fba8acc421d0f
-33 1 332 \\xcb8fd60421267d2f69bfc01f45fd3ebb9af81fbb58620bf1b9e827444dd404e401ffdaecae3f342f8eaf4fa500b1812cdc65676b8f462420558c2911ceedaf03
-34 1 307 \\x35676bec9d7bfb2538325eca201d1111894699a7ff463cf57bffb93f7ccb1b9598bcbe43fb8818edb1ab23b986ed605eff4204d2b2ba70f2103bb3ca6bc0480a
-35 1 59 \\x7eb99d9717703c723575503214eb06dc2bcc7c6c0ba9571e5b2fea7befd3e8f522bc3f980935efae06b62949de481e046080d673b46668de0450e1e1cd1e190d
-36 1 219 \\x18bc4c67627119f490607f792190a40fc36c942c9b39ac8b75b80f12419c0e8c25a154b252e1ebbdbb32cea68082bcd283e65739928e2c98481975687edf5a09
-37 1 97 \\x403590dd70e42c358786250c8cf527e07631e22f3e0cbeb1bde9f6926c48abcc493f1398f9008652bed443f0c0b59f49289a4a78d44c37a534d267642ba04609
-38 1 313 \\x43453d33ddab50014f2cb1cbe43338bf14b3708d7f9ee00000a7c7836a5122f1ccd39810b560a23444e0cea2f3462feda3c43dc87ed45d0a9dfcbc9d8495c108
-39 1 153 \\x3fea177201b9e4ecc88c3b826b9177c3cbe927a99e6245e66be02a35fd939bf414e6a2f4e3272ed6f46faaf1ea37ae676551e2e32174c2d20f367291de7c4f01
-40 1 239 \\x99ccae69a2f7d37002c7cb6c5c8130dbdedf7047adb1bfc6d870e15ac9cec6b26f7242277f1474125cbaa23885333343a7060daae97002c39b49a66877e2e904
-41 1 87 \\x67af508936ea877ff72a94f89a87555552c2484a1c5441f2fe6087294fcecf3ed7fc30d1a205d6f168047afc50cddf78d4d949c20771ee943b6c15155327040b
-42 1 88 \\x608b943bc7aa5d47febc59f09c97c7dd47438264df9a85cc4f836d29a9481657f87f33af8bd6665f1fa43ce96b63d2116d95b9db90f3bd3bfddf10aac1d3ec04
-43 1 196 \\x3b325f213b85b7a81b65f2f8ccb0d37e32d3772e26c80c75f9e01774d7029a6f619dcd8eeadb192a7ccfc17dab9d606f973253c1d58eda807cfc39c6fd353006
-44 1 149 \\xa4c5622b4a50982a17fd83673c9c1bca788fcc4a2ea6b937379a0ef43515f3c84c52397132478acb50a9855a5d2cb454ee029864a5934468a125ebdabff7a609
-45 1 105 \\xfd545011650eca22e015087a6c1f926455b8b557dd5a1b3a613d6eb67871306c1ae67434258f1758ec878b19e6757c5818fd3171d17ddcbdf038db94293aa700
-46 1 29 \\xcdd1bc200800f7fb9ef763f9ac7066c0ebc8f73e7cdf3e7fe235c6e4ad8e299eb2ad95c82f0b1b8081caedd912cabcd90893c79149eb49d89057f242f7699d0c
-47 1 48 \\xca35480525a6a09dcb53fca97dfce9629c4fffda043af95f0790a9e1eb849c7bed00fc95448ac7209482793c5bd0ba911ffb81f28be0296b0e7325f1cfe1930f
-48 1 302 \\xc14fdc0e21efcb01855364e5bcb9d9a5200d99f09542abd6d1a299180de91f36748593929a6bbb0578cd0ebc273907e3059a19ae2806f12b1e1ab10cca27ec0a
-49 1 274 \\x4326e43c8a78aa6923d59b756664cad18757aaac5b66226b2ac8d4a26ad457672260565695474f1dc3bebbc10a8dfb6e99235f3400193b6cf9a359b759a23b0e
-50 1 366 \\x517105569264794498727fcca9d4cc11532c0dcc5553f53e8acd42b3aed5ac1e80d8d56a4c8be4df97f4f3c2f99767eb7fc52dc0209873d7c540282534ce9a00
-51 1 285 \\x3e91e9bd02cd66d67a9f29a3702bdb9699ab5cba4ed1bf30cbed030f2afac63cdde03aa5e9f80f4ecc19eb533624ae87f01b5877fbc8f556a7814617c49fb80a
-52 1 404 \\xb47eb6310f3f9be129aa3d34df83d01eeef889eb12a9eb70e3cd44d64a26c92e7c102245cf661bf279145094c3ef1f58fe9f3b38dbd591c27b7f4a34c9bfc40a
-53 1 179 \\xb3f176f2a9801995b30e13f27971f3276df09c3342e8c351e7c8cce730c7e0046adda6736a70944b832f96e79d9f708c799781f8fab1076989fb75d24c1b9006
-54 1 220 \\xd510ea952c95ca3d21355fc6e78f0cfde9e58843283a2df920dd3ad855e4babfc23819b1ca29a7b60426787b53ec50619542f8318268fe4a8f058fdbc6c44509
-55 1 244 \\xea9779aeb86dbdfd05dc31feb9ee9aaea308ed920222a21b50e04aba5e847b007fb958b32767a51f2fa47ddd233afc1b917258f7de16f2568a41c73c3a013d05
-56 1 423 \\x3ea01ec72989c545fab3fac1273071e74ab569ccff6575e88603daadd8d545ecc9899af8c7d913584c7d5f4706be4b0d04deaacc1aef75b23a6821b59bd6a602
-57 1 1 \\x0db501c41e7cdab21cb0088c8eb4577e194eb940d8631416ee128f50853ff153a2c79609449956c68b896ec2523e578348104b561a29e52b686196dc6526c806
-58 1 102 \\x6ab1990473bd222b103793e2efa55e850045b5763a1df4bbf25f0ee8d04366fdd219433a7ac10a878bfd9ab3fc713262425cce2c3698720d9c7feccf7037710c
-59 1 28 \\x2c3311cbea6cc092cfd25920722ac3d27b1ba5d640e1fe3744acc6a70fedbda125157d46e1cf024b55ff856489dd278367fa9ba287b1c7605352abbbc2004f0c
-60 1 280 \\xc2451402f9f6b2c37acc498d34328c9e0aff105b29248fc76ed2bbef394e2b0838463b7880321ad7c2e69aeaef1b815c26a397664a190fb79cb20b9690598009
-61 1 192 \\xabc7ff4acbf9cbfd730a567aa9405e25a2ca87e27c27f046dfb134b8cc5760eb39daf2fbde408b088d72ef05f1cc77bd0a2da0f26638cc6bee054fe7fcb11b05
-62 1 89 \\x4babc00a63e09489f387ad09cb92c7b8d581689ce4bda24781a6e4a23bc2880fab70d92c3aaa8c9b74a655fc8b895772e035c69e1d6910ec0bba1075ddf4610b
-63 1 77 \\x72498cd449a2181a1649da651f65db2fd5165634b5ec2535242654973c229bbc68d831fa3e15589a671c40aa8799800243aa4b681fe6fc84e4da318af2a97900
-64 1 299 \\xdf08b80d9aaccfdbcc72ce4fc63b61ed8983119a4fa986de5355542100638d28d52ee0e67ebaddd6b2a684dc0bb8b95dd1434c597990267317844ddd60866c0a
-65 1 336 \\x790ddf5275db415c207234b6d9a1aaf02d60881853dc74f4c4e401e0ea633d1e31274b4b69880e7d588bac481e22d980a25f60b479aeff17a8680f4983ea7709
-66 1 306 \\xeac95a28751ddb9eed1d2afcee9b14100fe0455052a6f126770d158d11409728c2cd76c9457860dc9154957223413d037410a0f7d1d6a902e0fd0653405e3303
-67 1 387 \\x8b5d81cc753286bc49da234f697f9d71ec3a77720cd674af17c1253c58d082818d7781a21ac4090d0aa6a5a5d176a3658afdb5f3f5c18640c85faa219f5a0609
-68 1 76 \\xaaf8b7dda662fb18a8f8adf95e11860a3ee6daa59724f9925e2830a49647a7e27c0f24bdd87a075a158167480b73ee1d3f38a29da3708fbc77bd60646f363b05
-69 1 312 \\x8616492b3cedbcb1ae45a7df030f26e1b4a67c27d8b1fe6d02336e79ea6a4e90cfba06b7bc291a3b3633e0416882b88695db0b159cc6b3e7b79f4e5ceaeb700c
-70 1 193 \\x520dd618661b16928fb77292a1fdf4a29419cb52701b46b456383e6e5a1ef02764a18a716e0ecd96b7f7920ed8f788e30b3b32777cf04ba201c52b92b78b030d
-71 1 290 \\xf13c00fc58ae5580dcc9c79672fc10797485411d0b14106c1c3b80f056a1baf4c223e6a75a9d83af4b0bd722ef950f4a53352fd38a3b1fb2c7f74fc2286d110f
-72 1 9 \\x6a51a94cf396fa16e9d1a9df9c46732044032fe53b6531c75a611b35c94851a5402b6c20c93a8ae5d2fe775893e5c937bd74cb87abc5a398cf379c5e867ec601
-73 1 4 \\x996e87697bbe95aea1d8ea28fd42af4f4831dc8a39fa5a0dd7a606ad29930d21a12be294df631e6723570f402ab7cda6c7a79f39c6b3b1eb6edb449566dffb05
-74 1 394 \\xd9172718dcf4a95fa62ba8a2b5d06922367df38bd3ad8f1de23c703040a594ba524fb2cc8a76d835f0e478630061909a870a968d9fa5f353df5104b28187d10a
-75 1 184 \\xb018eca93e18fcf891cbdfbf2a6429b2c8ec5c673d1b9745feec9801c6fa7428fb69e22b5d2b252955c2dd1ddb79ca42f677be53c7ce87d07319aa8d101b3209
-76 1 325 \\x2719d634128a97c2e10985848242150e3b3f5a8fccfd08198baa0789e97d588afdf475bd0955eaf9328b67c5a08693a8e5de32e905426c3cb2401c743ff8a604
-77 1 6 \\x2e4ab6606795e0572b7de96d23852106e77c95497f6c904579709d216432ceeb20a9cb4d6c4f90b420675789579bdd77034891aaf43e78d60bc98f57bf49300c
-78 1 100 \\xfe393a417fd2d39a9bfcafc9a1f623f7607e5628496211fe1b3bcecc7911601bf67d639dcccd10ec893f5e0ea889ccbeb84eceffa15bd645b2bee1abe5dd580b
-79 1 273 \\xe1b27ad2cc88796db3a728fcec74cf27e5254192aa7a1a124e5f60cd281ac1a83379d4e1b75be41b3d877b8506e1bc622ed1e77a88bd4e7e298e74aeb24b4301
-80 1 148 \\x7d45ef90a0a91f78653173f60f9d739a1184a1a4bab22899ef36424e59098a016b8f0bee11beb49a92fb87885b4951fe43eec819a145d1e760f20eadc0dace05
-81 1 66 \\x39bfd6c711ffef73da70e37e43981c0cd31ab4fed4331d205a2b5332311c689d397b8d695ebde451941136c1980edd03717e97af35886bc9830426418791000a
-82 1 195 \\x493ec3d7552ead71678455784cb0be433b70dac7ef13811c0f63461dc1b00e17140dd0c93cc3dee5d106d651fa7b7d8fb6b32bb2462dcfba28656960c18e9c0d
-83 1 365 \\x4cf4f357b124ea37fae9f00819d7c5ea1d3bc2dcc34605925331db737de0dd43b1f9be12f5c6b3e62e2f94df4e48d4164b3da22ef5e325013a12ef3e5af04906
-84 1 46 \\x075ae32527616ce8ded51d7a0e941a40050b29fd7c6bf78f8f45cb3b9eedaae44394080dfdebf1f216953dfda27222c2965676db0d1dad57252dbb80b1d3f00b
-85 1 95 \\x2485a2c906216c0172caab0146120ab0a972f8f6c1e2e6769e3863da9afaf2b5bfbf2b5ebcc40d26491e9adb96d9df6c694850f887fb47b13348467bfbaead0f
-86 1 293 \\x03b64cbfba2038bd6a46a0afa7e0d2533bc933d1a9cce9e832c3d07a957d02400aac26104434be708d6747d0e010e4dea02a7d71177761cc995996cec8a0870c
-87 1 399 \\x40903f40696f0eecf86f2126638c5601e2ae8dca6204857404c7bc9a049d84ed91be78341a725cb46929071b79ceef6fc7cd15f407afeec09201426d4766c605
-88 1 144 \\xd09be4e83b6b44e4bc8ee7b4f42320e50947e9c632f5ddde51d692a16f80273020c5b2da9d709ae8c27997e9e0730c06e42c74f7dc293978f895cb6797273d02
-89 1 422 \\xe0ab50485b841e2a9428ecabf1f0102bb743f0c0bc2db237aabea8790578fe3fc4afd260cb381a3c129f403d7ac95d6056c2357cb001266e11ad592d790f720c
-90 1 252 \\xbbf0d2aabbafb4957ce077aac132e2c397ef1ab11d68bc2e7f040e9601930d70db001b922af0c2a517d81812b5a902fc006ce4b113947051c2f1c81fd9c18d0d
-91 1 10 \\x8ad066ae073fd9d8d5c907761585f1717be2363ec63b3a8bc9308c9c3d3de0da5c0e7f0faa3d6b67138142cac3109520cc000723bdcd096c914425befba0a60c
-92 1 233 \\xaae4ff8673938b7674037dd5adbca3c0ebd97828ef46fb220b206704813ecb8a6d5420f0b03b457c9a221b8a8402f27dd1208bc1854c3dd3e7e1075aad8e6c0e
-93 1 416 \\xe5c0a094bf68c14561f8e8c943d64baa8d5de733921d32d1db11b31cd3f66143daf10b578d3cbed0e6f5122cafca2b3ed493c0adfca1c8ae28b5f1d8fa73eb09
-94 1 221 \\x350d367520bfa454d4c16123143042671aaab1c9f068b96fd61a85dad2531c8a59788e324e719db33feeb7d127ad770f2caa7e7a4bd4a37a38dfa7c9c2ccbe02
-95 1 328 \\x1050b650ea8a9d081af69846f8de3ffc417679c88de4d89deb63751be2159ce51e479f248a7d648316e1092dfaa7dba6a86383da2d65626cb7646d20b1483d0e
-96 1 226 \\x69a0132f5ddc3ed3caf538d2c36e291a48801e1885a34b274591766842a506af8f5758bac0aa2b68a1c04acef4be11cef271ba821fe14017cbb984f2e848a401
-97 1 183 \\xc7beff8cb5b10217b031991c906282bdfe8819c52ebafde863e884d2ddd7d47f7b6bd3d736dd8772083e0faf0c42ecb2726475f0ab0587eb8b209a5cec0d6608
-98 1 266 \\xf7a243e9adc1f585c94b4c1ee28dada561d15490691805a5d082ddd9358718bf607662925d832bb1dc679ff31622636d911ccc8cf5fda21a13223ffd4b8ca909
-99 1 351 \\xca5f13496c412327e516ed193515e904ef723f5e2a4737cb53a238358b502d33c208cf029d398b949e61985603c95af993097f9c28c9635c51ebec73ac75380f
-100 1 372 \\xc97a89db4a28b312be82795352ae27cf9f02d52bc6131d500702e49877b477634056deebdf10107f6027559c2037efb6e47dc9d1c56cd24bd3596adef6deb502
-101 1 65 \\xf88fbd438982873feb097246d6eb9daad1a4f38a46a39c744c44551560d27b17e40a9392eb20286349c1f373c0865ffe1a5b68bad1d7949dd9003a136157d80c
-102 1 79 \\xcedbf7be48c8f581e813a9ef24e1a7fb6ad1cf20aa481ed7d2e6a6c127edd51929a6ef03d71e3ee6fd64691079ea2aae1ccf58ef9fa214d9fafc01f3fa9b530f
-103 1 14 \\xf43fc80b131b1cdfe7fcd42a9d5fa4699e1bc7fba1b7bc86d3c5b21da92e6d644fdc5b678245300f9aa815fecb04cf34f80ee9bc77bee0d08b6c91656ea4df0e
-104 1 254 \\xeeca2db53cf74ac5af3345c3aa0e0c31e6acee862a0d034e9fba681177ee53a8f01416cb8a47152f0a4c09f696882da8674db39c23e0392943d0c04dc2bbfc0e
-105 1 216 \\x77e14ab3688491c1bd4f1f09b07bca72b36c8716212f8fcfc86383d4fd652c89f2e09e9d55e07fd7cefdab390d945aa1e036553c855bce13f6653fb2c9ed040b
-106 1 127 \\x0ddb2d365cabc9b04297abed9a53d32ae5df95ff79d48fb16d28d4ea2907738416a9d2cf4e555e0e8a26b2a790b55be51f94aba53d908035ffb3193a934f3a0d
-107 1 38 \\x148e17d81fd268d6751ff297bc273ac70d446ceabb8b086f8fa011ae88d2dd82ed18eacb3121911d1bbc817a2035393276d8f02b302c1a008dfbe5f1d4bea702
-108 1 154 \\x6c7e4ed839190b8d7b44732468ca3958b77842ebfbac5857816609654d3eda0090983cdb7ba22cf6d9810b0b84a0b96f0b283b9cb393df796852d939b7cb4306
-109 1 176 \\x65b1940f05e41e34d6b6dc96639effc7b3d80ff2568a366265c4aa388b0a2450b5b95d912e692beb4128334c6dcb563dc4a5086e41e883a3f93946b3e7bcaf01
-110 1 51 \\x2b17fd72e010be8889605082e6fef0d86442e0d9a7fc751fa1b623083e72b5eba978bbce9e44ed20a9ffad65a7ecd8c37b9cf0e6cca6c3168a5dde8745fe8705
-111 1 256 \\x0ea56d16b86c9f72ec6c26d61246f5adef858b50639b4b99d52d2af12f2b02782447666b11ac6edd2dbebac81ec6c77cbf8e06c623d4d4a7e32a871ecc65fd0e
-112 1 91 \\x80ef1f05e07cbf16dd8787529869ec0d55ef59458694b797234298eb16676f5fde78715ff8107371c3e07a314fff71c3643289fde883caa82fa78813caea6107
-113 1 337 \\x8ac287fdd5e1014374617781a8b1c01f3dac0d9711433221217fe8bc1ec7e788a2c058b6dbd26b516e9af56df0826e62c30d47da3f0de45c68cb4d02ef753c09
-114 1 99 \\xc3a4d4262d64234fdc9ceda2330422abd9789861444ed907de06740c2672f5828f103707802aa3c6e2320405831ac56acf134761b1e3176ebf7dc02ee4fe8a0c
-115 1 180 \\x46d2449355ad7725abcdd0232b6acfb6da23a8fdee8007d467148e8edad769277d4c94953eae37508f8bc88aa538acd956de6a2aba17d29f217cd1468c615e0e
-116 1 23 \\x413cc4547dc6289cd7928018672d6d9422a45087b90b8cf1ecdfc1c02b39cdc956bb7583224aefb67bc9bf8795e652e3b396f94613856c5c023e7ba659cb1309
-117 1 47 \\xd0efc4a87ec64bd4b4ffe7c29a6eb2fed49097b11681234923120919661bdd96f7bc3efed1092acb3b9bbb784b9cc0101c2a17ec5d85faa4105d6a4facdd9f09
-118 1 231 \\x33caa132187aa4610866002d67a0b7091694e42092c7e92af306e8ff97af17c01d95dbda8526ebbacda51cdaa1829890239f008d6363733b84b9bbc297697f0d
-119 1 136 \\x0e79fa17a996c234d8a603a2b8b0fbfca80d6c99e147a16397db77a48947fc0851f73c7303a546bad28c02b74e4d94da53a34865e8fff2ed4b0b93a422373504
-120 1 22 \\xd54b9e577cd509985d0ba1de069e878aa62bd5edcc9f08e8528e91b66250da20febd3f01f06ef57af6d621a62063b09fb6a918600a3883ec57580f1d204a7f03
-121 1 238 \\x269148d4b16999f28097ee6d385320fd12eea8a778a010678567107d8cd766750bb8f206fb94a242be2d5c16cc62267dee990205afe6bb1acd58512e862fc402
-122 1 146 \\x78e600f343bb9edaca806d13757c907dca2a542afe764e48874b550c4991e71e03a667243a00dcdc9a29abe47395efb2cd9b84214d92ae0f5604b1b0ba997e04
-123 1 409 \\xbf0d0e60cb05e3ffe0da7212803f31b64e3227e856ae09c1be13a1e576bb92a673f55bb97d089dc075605f00d7808ccd523a89e806a52d27b76fc95ddaf0da01
-124 1 210 \\xf5635f98e610ea78c2077719b6718ec31ebef1d610c57b86aae93e5566ad81ecad4f21933b8a639db0b126fbbe941a5d6aff717b595f804337d5eef732f9bd0b
-125 1 140 \\xa72e746bb0ec45444b4bb23ecc034007c9023a3ec25f88e523d71a691af860c90514cf9b357a773ed4604aceac79f79eb1742be5513eadc0decec12721e3b20a
-126 1 403 \\xc77fad22775682e9b2f5a3f566a57b8ea048a027018f9afbca12dc5c8e15e49956cc0cb30e4d046dce5a5eeb47af7eb21200cda3d4ac237d9070b3e03371420a
-127 1 169 \\xf4e3f5f145a67720e9d6e275c27e4562a6361893af263c30e19ca614c6dd77c5eac299658489b8a141ec87ec53cfe4aeaef7bf5686d6e09208b3e7a7a015680e
-128 1 327 \\x9b318efdd839af15533ccf09f527426ef36508f989440c57af127bd90fcb4c4c6ea6c7c33fb37a04fd61f6acdb9fd59d2589989845fe8093c5d69874ec1e020d
-129 1 245 \\xdeb797e8d033dc7331d6f1f113c9dbd77ee20387c89e9dd64715719d9af224ccab790c9a2810997d61cf7adfb34a0c9f441404d938ba4b74a284cc3fdb62ba04
-130 1 75 \\x79a398a0adddb56067c2e5a2dfabed3dcd6a32ac68de195e4858ba2c5b8a9d3f0d8df3871be75a6a4efc51934bbbac48a62595bd84b487ed0e4cce9fae91df00
-131 1 384 \\x36478e32108dba90ff861e5ce0715847fbd90af6f7d0fb103bd43e4aa02051a71842aabcaf3448b65f9bb8df608daf17fa3531b4dd5903d9318bcb619f6d1906
-132 1 323 \\xe8bf7b109f00c1597a9ab0366f291d2f1d4c8b0548d1218fb6d5ba19f1a2a7f7f173dd932d451b80400ac573d207bb044d1a6a3171d587f0e80c02a004760009
-133 1 225 \\x5b95285b10f8194a48c0cb67c3226763ac9a3e4e6c9d60231abe13e8343b08dd00856ee9549b1e2eae51d3943dd8bad6dbfddd11b4c13a47a70bd21c6738170c
-134 1 103 \\xe3483e0e420ea325e0e3e261589cca8a5f3702bc8a1f9e5b0e32cfbc11a0f181c33c35c80841b11dd5bea35d536056c7b07490e628d0c4f3f764c67b3f9add0b
-135 1 30 \\x59a0b2a20096bdfad5f538987ae1be424804dff965dfb615886ee7eb3c216014e71fc6d49a11aa6622aef4d5078d02dc9fd339bcd1cd98c1c2146b27331d0807
-136 1 340 \\x4c61a662e34bf25c47f77b4033ed03149cf146f2bcba8340de58d33486e62eeb8dba5a738f0434be340a74d73444f7a865a43f515d64d4b38730d3620e8a4105
-137 1 173 \\x953de3719446cd1778201af61a75d562b7f197627e5d514fa379880ed59462581de6d3e70012f3632aa60a0d46e220fca4a9f57016a19e9e601cb753bc142907
-138 1 200 \\x19765517b1fbd66d1ca2192eb03bf86e110b257493253ad0fc49cda7148f20cc24cd9b4a0d6dab0c51557a3d185cb76c81f202f54fe3fdc6f617db841069160f
-139 1 206 \\x78c4c14f114d8b4681d45670a460a13406fe62b39097e4b6cbd5d31d1ff8f598a50c5b522a02358f9137fae7a59f0a64d886176cffba1e90f4b7a48b972a8d0e
-140 1 16 \\x20d7083dcc521066aaf6e58c3f472da272a2e5dcfebea94639ca8a23c98b5dedf7d7d8efba261c6f5c1907fc6c9cd7c5d5d69b78381e020f5e3ff8fb3e49c909
-141 1 157 \\xcd7d9cf98eb51113bda1a4091633b86bc939b9a3d145be429900f4da6af48f91cc6cc8dc30a816b95b5b9e80db7700d57a716a46e0dd3fe5ff1db678fce07404
-142 1 373 \\x2b064e0482fce42c07862f2dd161a8ac15b23083850fed5b8e46129c8a81a1502bdff17e7f619895ab8f2c5e7651491c3a2215a667d015cbd9c6e7c50e51370b
-143 1 408 \\x51cadfaa60c6204b0dad9ec4d9c017c0b4e11389e74dfaf841565fd7cc3a28f4c30aa02364975612681f94262840d00bb7648b7c1844738b4ad208e328494b0d
-144 1 119 \\xfee5e55dccbe3bfef6e65016d76db069daee57ea117acf09e757fe4c549b3b3b40c5eb0e7d97e66c69f8b85fd7bf89b9c755ef6ed5d0d09829f0b855041cda04
-145 1 189 \\xabe0d22a3a2d5dbc3360f4c8958a10208e450d3ed43c8288d113656923300167f3f64f49955d5e0ef74afd5b53b822d29c20aadcb3215e9ce09b344f918dc70d
-146 1 314 \\x9fdfd72043b53469dd56031f4f720a6a30fc33b67cfe23c4a2fe0859d93f0aa2239385d6654e817f51ca46693f3fa4c0590672f77edbda4f61d209c7e12f4606
-147 1 320 \\x726e7866b3fb527636b88a262e2a1c774c824bbf73c20b4ee02d3992cc7bf344af1e6813ff9acf660af1174ce41595fdd85a318f938e7223d84b8d99c9a01a0b
-148 1 94 \\x5e3592690304d34a416c16243e0d9b53d540e4eab3508f84a5c89a2ea07d41a33803d23d041e5261f84fd8407a5a328cc77abb2d29c8e8412021b925b480290d
-149 1 115 \\xbe4c0d1d4a022e303f70a64902fe29efaf9ce9e67f0aa8f9a2d3cde1c4aafc5dd16af7c5cd19132e7deacf096b2a4da88849a8a3f67ba492a3a7fef04e469e08
-150 1 128 \\x797bf52b121e6db498f4551d616f24030dae06cbac0e807f38d8b056b0e6ae881c2e98f7e12adc45f8062bcd61289564f8beede7cc12640b9c6b1701af759905
-151 1 388 \\xf752ac1c3ee8f80d062db27d0d895db2937f987c2a07123739261249e35f5dd84237ca3c7eaf30a15ec5d66a26c7170c5326f8be6dc155df1c2fbdd703a0b605
-152 1 259 \\x66fd9d26228e83da82fe12cc328efa5de5205f7ed46a922503a75ef07584ad750d17e877657766fa6880898e9a26d5e08eb0eedf2f75dc08b63cd35fea995a07
-153 1 142 \\xc9e2f7e7e0a296b8effe7bfb7eb167c58767d86838914d2a6df6c80fc27548b86e500be4d52574cc98b07afb2b3ed6eeb42bb51208a903cf5e68a29ff022350d
-154 1 236 \\x41a821539c425a7e33c6aff86bca357fe65e7c213f3e28888abf8f38aae81bc9030dfa7bd90fa30d45241f7587b1e8f68cfeea9de4d9119f06ac941be230fa0e
-155 1 116 \\x67bb4c7451045f6d0e1cdbedf4b632458b5311e2dd3f6f70cdd78f6f9f9a04dc8a856d43509b0ec2087ef3b5c3ef8f38bcbd0de7d4ae0d83fc78ee2e744fad01
-156 1 417 \\xe35708d2f2046c2392d1c0105db5b0dac076f58221754d9a41e2c7de8e8190d2de79f123ffaf413ad0523f51b6a1d5a6947450cd733b3d5d55500122c87d600e
-157 1 232 \\x70634f4a473e187f22cb92167b217399dc6c948bd7e2b6ec5f72ef63b3c4b70fb8c7362a56765cf7dd80258fb13186b0c22060e36faffc17e3604893bb2e0108
-158 1 13 \\x03789180d1987fa0eb7df7cdc7b84415e81664f88debb855b8dcefe7a45f691c150c6e4927bbaa320dc96849b62d2d40a3f81414f44acf0803c790df41fca202
-159 1 55 \\xed0e7e227f1d6f3dfdaf4f20c84a7aca58cbfb6f9dbd53487623db84bdebc69fa82dcb2250fca08fd2e8573d788ec4404971c02f8a01e1d5d96c1f450c654c03
-160 1 160 \\x8e49ab643e8de04dc7279747d99fc974ecd6c041878e6a989830bd55845acb47c8cfca794a12f6d16849e2e1ba5e94acbb6380226c70da422f92a3f3c671dd05
-161 1 137 \\x4b26e8b76e71fabb5bfd2c8fb505d3d93d1dd2cc2f00b974330bc6cb7a8ba41254aabc1f84d1710c048c34e2ba85013bd5adf4bea5149eda16fb644f69ec3a08
-162 1 396 \\xc26e180985ba4d56c8f816e239b29d7c9a5769d538e6dd208805e8cad233e30ad58b22195f4febf516264d4b7c005c215b9143b3bb49ca439cfd1780141d2506
-163 1 315 \\x61a801ea1f011dc5e417804cd0b6f2c83275495c164ad6f39d8d9aba11c16dad48505d6b8be6235c971d5b54512b0798e8ef982437d033617798d2ccb8dc0103
-164 1 68 \\x0da6a05ed8c7cd6cec38982d516c2948149bbab714facb6135c361e94803bae6dacdbe190a19b1e7d881ad630140add3972e4caaa3a915be6c1cc3cd5302c10d
-165 1 3 \\x3493543bc5cb14fc5d22d8ecd94213a535dbcff5f4c78b427d110332f3943079c695295e2b54be4e4bad3f8ce304f854cd92c8cd19c3bf87b0b950357f547b06
-166 1 411 \\x3b08dfc84af97c7d4c44d5489e77941f490f2c09a84c9413a3f9a9edf88b1ef06fcaf0f9aa33f073c16f0890c5050ba6c4a87b926055b3e5392037aeacee8a01
-167 1 175 \\x75742be71077ddb572bc33a63824497bf04b00ca011069d2494c79478951b3b4088cde0eb60de13fd1f1abb51411700a0887b330cb09d5141c1ee1e7c7d17908
-168 1 246 \\x60d397e47ad861d64472b2781ba363371a6469c045c25e2bf2e6dc197006b5946635936f7ae16786f9c6e131e8ca46b92ddd6440fef035ae75c79c7af4bc2d02
-169 1 43 \\xd16241e7c4157cac6e672c8dd30380ed4b71da7d57dbceaab4484dc051cee73289e4fc361b6476b76a359f51e851e0fd020516e4c7fb7f4edb59b481fb9f5200
-170 1 279 \\x035572e8d9eccd1b1d47f3f0e05ae1793cbd189229b297c66aac919a68f31c2d59529172ac86997ed0394f10934dea42e0ec8f85a4edbbb9b1157970031dbb0c
-171 1 141 \\xa22950aaccedd7febc2a7fa93201ae0cd2cfad9df2e7d389fc445fb8a745191df9ad3fe40612574495fd319d237ab71674d9b564997cb777b3fefbd022666204
-172 1 208 \\xe4ee9c2931ea26266c4ca9df232cb9b1799f034e231875d5f8c02e030997c8825964897639679ebee832f111971a905aad82d0609d8b482638a9f6a395a54306
-173 1 118 \\x286d8314dd09482e77332afec7cde5d8a53f4096b0ce34519b1e8ce2b6f576d2e0144880df55eb1dde8d193ff9e1e1cc9d813ad5d3ae6fe5eb073447de7c0606
-174 1 419 \\x9eeb2f317246552ccbe68eb5acdf453564e7e7d05f4e18f81d40bfb4d7949c2c90674987d5b59ac6e8e703feb48b4d066339b387bf2a238647e060de208fe808
-175 1 27 \\xfa3574d3c3188bd8200a440e96d9851514362578f89545d10c22a6e1b2c6c80468817ed52a8fed4b849a7144448a0394094e68efec61f6553cc8e806b13c7d04
-176 1 229 \\xa1a765e7df252e93edc6b91919f697d254d780d334d952b3218dacf734c06fa6a721edf9eabd95dd4746446a9030d5adef422c7d9b056d3369f238e81733e001
-177 1 203 \\x80ce3e19385d5fcac590e4ce300db6996d976ea5c9509a89cbf94ced0a372de6d92d1f7a813fa03a906a91183b15c71f9eb8181f56daf7113361c6d1b9cf1e06
-178 1 126 \\xe20e0e7df43476ebe9bbf65d72f4878fdaa71b809869ae775fa67f620eec29a5b88d16477e052c325373b3750a500afe212e3a542411c2a72a98621e7a003a00
-179 1 158 \\x7b3b19bc8f4b93858e8b19e969da4510ce6f00d3661b1af5e644b3a7b723fcb04ed15ab357b401a994c12bb204a73318feadaa934b242e4ec85c709d965b1905
-180 1 230 \\x46c014456c90393a021674b1905ccb1675660689d25e571240eee8f7715a429a68fcc27a55aca54ab57898a1c12ab2fb0f28be588fbe8fb27f7ef06025e21600
-181 1 251 \\x7be23f1e43c36d0746dfd7eba0dc430ebee593ec7b6ecf6e1adeace0becf8ca468e58dafbca1826396b615e3b452fb02c7da4e1adf298723706e569224ebbf09
-182 1 344 \\xd95e59e13560dd1b51862875b13520ac0963b16ca12b603bcd1b6cf1e72425cc3e1918cbc0892de5fbf0b48bb4f7fc9825d8203600d61baf3b5e53bffb22d704
-183 1 12 \\x4f91fac75181825eff9c22fa722c286194fd02c297eb516ea8dcc74fa5fd9484008a47a067a4ec66a45f09c7146048f147348127618fe06f57fef6ffec3d8e0e
-184 1 185 \\xd07b3851f7a3b8fc9bdaa1d12cbbddc9c009f5a62d5e4837bfa9a32eaf48f9475ccb49a3bb94ef7101b6652f6c6a8ba45d6a1eb2fd437e4e839bde0796a4e600
-185 1 8 \\xa8930aebbca7d87a12c52a46d70731d9405eaf2d52bd4916949fb846e69b1403784b7574938477bcd3e73823a4b71ffe495a91367a25e7ce7948687e8fa8e606
-186 1 201 \\x601b9c7679b6cbe0fd8d1ee40db0fc5f4821bc703ddfedeff659eaf09a02c7f2ea2b75c775311d51023b9459cb891e349b8110b11050af37e622e7ac7d25440c
-187 1 162 \\x834d7b96bccfad18d93057abefc21218e42496aac853ac17d3ff4055f4fea15e02f8ed4c1a982083af46af387ecdd6b773f6718fabcf882d2554ce1fcb6e3c07
-188 1 289 \\x465864b48f0379826f7f6e7ec072a341310d446471dab1856808ef27f3a12fc2b3c8f3352320e00ba3ed0c46b5c84e28e8a929beb45485ec906a2f21fd034401
-189 1 194 \\x6e2c52be3d576d954299ab7d47276f69f91830788c994c0d2dca550fe9b44cee09bbe5bc678a9d4e2585d13adf51386f5562a2245e943780819aa2b31988370f
-190 1 424 \\xfcf76ef5333e6053d1a6042a25bc30bd90a01099d24f97e6e6fc8748dc5c57b965d91823cc5b0010f37486da189215bc1852d0c97c3824bfc5c5920c68419109
-191 1 335 \\x74b53036f87bae1e04ab4342281dc8136e7d23559c3ea531e93cd70512f563894f1794fbe76fb72742480cc99e30d2c9e72981a20009b936f56a148737c9de00
-192 1 291 \\xda0ffc53e079a529b22cbb554ecba65e555f28c9531d2bb6d7f6c8bc83e7108ecc651a9f5c2c29168ee7f8a9019dfca7109bdd41bbf36865b3636c2509dcf504
-193 1 228 \\x044aeb7a7041ceeb787c62dc829915e8ae4e65f4bbf503bf5f709c07d44da7d5734f6ea90a84b728c9bc71e0432087c1270d55cff9f731dddb4ea6fcd49e4f0c
-194 1 133 \\x7f8f550e51b3ce47b08f3e2b1057f01c06902bcd726459928ac9ad5b27fd0202177b26883d80c4070bf02bd06e7362ff3b20b974646181548b9b85c2de81fd0d
-195 1 123 \\xdb7cabe76f11af7b0ab360b7e378d11d804fee443d946ac9d9a514b292e65099100a9f0dd1e8544575e0e28edd6f06312bc1017ed83e9e10384c0954a666700f
-196 1 347 \\x3aac91c176dc389aa6312e505030e1f9d88c87abf50716258da9ec9ef74d924adceca7c7470ed20fd255716669ec75865e1265d7286bfd8ccafea698a540580b
-197 1 11 \\xf0cc3acbba70349fc357b14608d59f2e95fbdd15b04ab5f66565cf13df6c32f9338469d7aab41cd960e85b88a54bb5c8f21caf1a4a86b0537a457646475c3003
-198 1 295 \\xd25131f8cbfd3be8e065d230a7b10eac19ece0a1158328efefc7b853f694ccd2d6545318fb67c9e307fe4af80e2405776888572c78999bf185298ee99cb4b90e
-199 1 360 \\xf558f66bc18f3843459c06b6594b96b57536589aabfd4b7f0ab925dabff8ec16ed2c75f17a8c4237f84d1707c787cba2ea6065d3071314ad63f00828944ddb0d
-200 1 414 \\x1f4fdbad76137f581104619cadb6212ed82c2d4d13446a97e0b0bcd9334c62ad3ae661bd7019e6fc29bd93e762c3dc19952c454d8692af6c8b245cf7e2921907
-201 1 186 \\x84aaeff3c6c5cd040eefed16b61a21837c858d438068ec7cf0c5177aae8b073be5aba24e994c3756c06e50e3337d835be61504b6ff09768e314e2ce77d127708
-202 1 159 \\xf8dab4822e1fda0b1c3e7044a08e2efe4d07a07dee68b487dbbcdecebe321da8c772f994d58d14d99406c7443160a1b4f04e0e6ec2eea9ff348f3d241013ed03
-203 1 298 \\xbf916eca555b749ee854d54f53c50ab59570a2e1d9a3efb3b17d8f60d5817c8a3c9f4d167824271a6234ddfe93ef55ab26f269b5afe2cc4aca91b705a8956306
-204 1 19 \\x903b9aac25ddbea29da3cfac42f513a0a37c37e0c33dc541996904455e43184c1b03e3d9b5e5a03ba203b68e19a24886532f2c3d65ab6ae49e32e8357f2cb70f
-205 1 271 \\x6b675249fd32f9a4a0ae57c41979aedd3339e9c29451240ff09f9f5f6d2ae2bfcb7d8e72c7f39f41a3627c5159b4d91a62335525400f99d5368159c8e958ea0d
-206 1 34 \\x4e51e1c759481e2187112ea834720a1597a90ad167ed2a2681a8a28be2cb9fa43b07d47e461512a0e64bbbaa2e1d182c320b48b7c99a482e7e5d938674cf920c
-207 1 401 \\x52174b01a6c63d246702be0a3c7561022645543c6848e571c474f4171428dc41d4893465b952a4119b86165be928101e62b5f7218596bb583c1a687cf716700c
-208 1 57 \\xcd0f2777310bf4891a6e38b125d054fc41d8dc00f79a7c2cf901eb3002b51fe0612f2e8517f7b6d55290d046d0c2447c210735e1efe11bbe67c48138c3821808
-209 1 209 \\x1b9926ce0e6e52aeb6122f98da010d00abb214eab36f381cba3b7d7371c0db7675606bbf6042c844dc80509ba5ce9a2ef08dd02b8db73521e5f13039730a8c0a
-210 1 188 \\x2a82749afb386b28e10f6e54692d9892cf865a4580360e6a3851438e423f00db4df6994bca61bd530c09fd4e7fd5c8647f61528a80e6580c155854f83f91fa0f
-211 1 80 \\x8d89eb9df3edb7e29afd6b9acb6df8ea77f27e7890a5ee2e6ac50d199b06323b4f42daff121e5710a867bf93e02e5304c5efe3660f40923ad0674e100af17901
-212 1 86 \\x2c992ae5be9fe5a961540bd2dde2101784397a85b52eb079887a4932ea3e47fc3f63435497ceba183e3666567f5691e529a2df48c0a748a561096bde07e93409
-213 1 258 \\xb902ffe3b196c58d58889964647a46aa7b9a18708f984d0df16bf0b039abe0bc16b52e5aa14eb46004a1ef665ff5ec2574c6a29a70d3c99d6047f5c00d187801
-214 1 275 \\xa09fbf0ac2da59890ca958fcd955c3ff4d5fddde17a2661164bbab8548cb9f6be3f44c44847c8daf30d372b65cef4e0f7e3c9d8d52cb016ecb0f9467a3b0bd02
-215 1 255 \\xcd3efea22f41fa37723a6d146929083cb12bf0503c9c5c397734c9289e97bb70c62140c3fecaf4239d931e1235d2009b3ff97cdb4e0048020f3738dca5ce5707
-216 1 63 \\x1d42885a150a86d1c1e3684dd85b6fd1e47b7a023fa7d9faa370a4c7ef126f3aefd3ed2b8d6269dff0e2f25e7e3b022ab17e5777e99e8a06b6ca6ba50ab1760b
-217 1 163 \\x69f537f9d9f7c52486d169ffda3771ce00b40cb5a7ee35524d5c02ad79b43ae6e7890a2e3eb2a32747f130e0d88646ce034474b84d818df895971789e7330905
-218 1 178 \\x104d81ea943498baa9ce2a4d7f5b1445a04c1361abe4fab0782e3bb32644130788e2ca2d8088e20dfd6db396eea3d751a875ddb345ded177feaeff0e2a72b704
-219 1 375 \\x2162961202112757cbdc6aefb4eb6ceb84512f485954611a2f0faf6bb30a8c00d28d837fc8afc33303af81f3154e5bff6fb94dc6fec76f48432c75732d052a09
-220 1 53 \\xb59d8524cccdbadac872d6bb727ad3a6f0b16a1ebba73ec2f33b249920e6353338bdc1f5e8fc069be378efef43df1a4d8b5a7a5d461ae4f8607f11ef0e4b1a01
-221 1 104 \\xcff7af1c9c4c2b5a1f4aa7ec4cb3c54675aa3771af1a5318f23766ad7014da1ca3b58f815f302086a724c6077e5668221fcfcee657d0838faf4d19e583fa6d04
-222 1 108 \\xa356b786566d995cd8548fe571f38ab9a72c66d08a5addee89f41e37c3c9fdab0e236f67ae39ce3695d7ca5d6b707cd11bb17151992b8959efa239ff44c0fb06
-223 1 5 \\xee78f1134a687f03113eeaa6d8cdae9c37e7e696656f1ee5d226b33a68e80a1598215ae88a21ade01d2791595d22b53a74c7602da0a58f6b7057ffe7cc61d90e
-224 1 370 \\x239e21b9225de0e0fd801c1f67facca30afa473e8f7ccdc6b29aeca233a06ca5fde262f2bfe8fe35c680ef1ba178236e3b2661b2a8255d8181ddbece30c22007
-225 1 284 \\xae7d1fd55f4a88a36a897ff5e9746b9e65f530995a703fe48738ec3070db728138320cabacce12d93ec5ba82ec056124b5d45e712e3baeb015c997f568e3ec00
-226 1 82 \\x65fca4f1eb132ff5a71fe8090a78e90b08077f667b343861f830e62eb328310d397f27cad7e50999d45542e21b07d01e35bc7cfa068e1891a5d11a49c2758906
-227 1 211 \\xf7573708dbc71c6834783824e987198b4c4be9f0e9330375938810c7e340c6996ee9741aafc60d7f0373c6af5e03b0272bb61b3107ca1f22312e033aa848270b
-228 1 386 \\xced2683d47dbba9996ea631c75c27c541d0820a7200187c120940446d464e96737cbce172b8ed38e58f9af7ad3b7241dd5ab08d5849b71bab4e72d49ebb6e509
-229 1 31 \\x0a3cfa40eacf30b940402b642bac3ed1af4d9895a21270f9af1705224434218ba7d3683f362465f9b34c0a3ebcc86bd7c56c6f9128a3b5145ae304bab7ccb701
-230 1 390 \\x96175efb63fc347cce6f0b84f6b8ceb44a1157ad4003ee5eb1e310a47f32b09631852031d7706b643627b22bdfff5de0422827cd00d6e917ae65dd4086059708
-231 1 398 \\x46299728d52eb8c58f03d3667e28551486fd676039858e297f015cf339082b81052b6e41d77fe4dcac569fd89973e8ac53820c32feea6d54d6d2f99794f51105
-232 1 333 \\xa050fc24f9618cc2d1757611598d1a01ee3e968df07759c4c9f3a11c7d4aee9de1039b369832389b9fd67a7f4d0dc6d33581fa3f58b22957ebefe24b0c9da209
-233 1 272 \\x318157ba7594e19353899da2c69fd051bd6989184e66bee95c58262a92452bb98feddb5d9bf3e3f2e3b50a2aab62d8de019757730d64db8e4a9804bc8178220d
-234 1 171 \\xcd9c4fcfdb41ce17159dd1db86efecea79693aa4381188d8da4c2c8b83457bdfdc384b5da991ab9258f09b73971b6529efd4003988815406f97ee7b7d624f50a
-235 1 395 \\xc560ea1c250c8602469c41612698d257bd3b0f1e7de8bb61c04576285d46a52ff143c1c1a16972192cf366b135ac9135cbb9fde6bd8314574302932799515105
-236 1 54 \\xa4f61cb100ca1d10e577f0b40175bce4cc484a94dd3c80aa18b9cb4213dc4a3af35273c7386f213a4137257b26b69e590db9c05a202ce0b316c419bd06f2fe06
-237 1 21 \\x89ef372286a58cb3fb9e6b8daef283ebe397f10196ce01c6b164e25ae027bec3dd89003d5d06e33aaa93dc5f4c341b8f23b24117b792a789d3052463904e9303
-238 1 2 \\x154749924b90b3684fd1494f78d1bcd2fdac242750dc662da07ed80e15612ba28258009545adc0abad7e660aa7afe20e16b281689bc175cd719a6fda1ffeba08
-239 1 106 \\x5645a428e30fac23797e8b382a4a5a31ef4beafa350847c24bb65e58ee3a5666415e96bfb5f4b2e13a90cbea9427bc0e2ce790246c85e9637ce18daf4fb61506
-240 1 71 \\xc35eba635455706af074e66093e9366a2c3ee68f32729551e1a97a0c6aa252e4d33bdf9317015f7374f10f0988ddefa683a263308cd0780c83f4c93e903c2f0a
-241 1 354 \\x0dd28d3352e8d26ac247c657396eb730d2b3ffbb33afb8cc98e358dca3b631a6e4ff2c57ff936750e18dab9ba0f9be384fb69c575aa596c3bea4e93190dbab09
-242 1 147 \\xd2f86c2946641811d6cf26dc9eda3a987cd676bf37b856a70941712be429df8b24351f8fac04a4d7a5b6713b4d0c03921d264b39f2022f2cfbe2d4f6def19e05
-243 1 174 \\xe74805086a6eeca9b5080627b91e0edbbcfd6000f954f60f2373ca60c62d63d5e8257624c37f384b5c597621df305c46ca5320b4989e78346e9eab370852820c
-244 1 267 \\x0e22b803d48805a37f12e1d4d22b079197caded8d14f31c1d9b299d65e6bbc60aa710b1064233cd1ecf060ca7326b94c9c7d9a352c374293bd1acd6b6dbaae07
-245 1 111 \\x179592daf01a5b20828be86304e7f4d08aa5b243cc03b9189fd2374af88af98e5a12d9526e1f8458df6f138c3d10a188d7b2e55d994031b2e482675daf7d7d09
-246 1 393 \\x9dd6d29862e540eafa370093389223dc500585475dd6c614b1946380eb1c86d58b1dfa2f16d8e050a20f912c27736258ae368848dde400de1d4066ab20939702
-247 1 234 \\xc3ea18c1271010777a3617d0a3f3b707a4b02886e0947261ac131cc3d94cd074f5ed9e8f7af22f46acbab7d2db686bcf044d5219e41176f30bfa35a1e0666d09
-248 1 294 \\xbc501ff2b5c75958dd3459aef2d872ae3a6107c9f19a83d69426f4614a23a417a5fdc19eea618ce89f1f3a079176a509dc79adfbda37c3493a76b4858389710d
-249 1 198 \\xe146b2bb345a370894709c625744d319b94b2bd4415f87922999bc933d53060d5e7afbf19acea4df4e3310af20f3f52ac972c0d9926c5b41ae1c70b7f8e3be0f
-250 1 270 \\x4c24775d8b8624ef6faca7daff66c7bf536e2a21d00c473d759d22cb619cee8a5e05262260d1ff682cd43dae09fc1ca12b57a501ee651ef19e0f0931eabb5f0c
-251 1 357 \\x30007346d87cc586e50ad0f9649e40cc4e7986062e28f7675a9a79825ab20df5e5ff75465b99c51eaaf4fbecf1d4edd7ef274b8c69a207e3a629014552496b07
-252 1 217 \\xc9bf579a9a55990259e4bcea8f9dc6adf9547370afce68ec0adaa5ad822423ea3e35cc45cd2a6c0bea2501d7e0e5abe56f9cfdbf7809b1db466bfdfe64d74109
-253 1 402 \\x717a0d9de718e22f733f47ccddf24276cb5f51babae44f3dd2e7e79807055b62b96e1719c4181f48d9da1f07230de5c6477f238ccf54bf2f50ee49a2d9f3920d
-254 1 218 \\xe474c67ab625830250efedd1391f9a5f363f49424d43bddd260e32dd83ccdcc751c7de7b607f0bccebae97ea0d2369977855f91d79711db91b8d891734b96309
-255 1 125 \\x1b00aaf7ef4c934e00d5709c19376e2a327217d68d87b6bccf57029495d96f8910419e13dba03f3526061611834fe9732586afc8388c8ce8934588ce6f05d10f
-256 1 222 \\xb77af7f3ef43c3fb6768d5911f11f1552c565f5c402183a0636640ab4ce94a616f90eab8bbf03face17fc7b17d364e4e78b05da9ab7a1ac0acfc08d0c6d9470a
-257 1 212 \\x377f50bed5ef61f3b60b07faf84531b177a5657c5d65901468892edd7e0f3cf6664bc14fde8e3a2d4c9e20459cdd1b1cd74d221bded0c8338241a80882e86507
-258 1 26 \\x9f9fe999112d54a4cb65e1c98dc3f551365545d6760b2bc182b07a59070f891340be99b03d99b714b70fd5c7cad23d744b4be6d19945b3dfb68e797060327e07
-259 1 377 \\x01611f2043d2f2f914c99fdbc03e15b9ce109bb9967a18dd775f575ea01de83b1ef3a5f4037aac7c100bf05786840c39aee028ef611f20fcd211eb719190dd0c
-260 1 278 \\x4de7f8f05b764f176dd86e261d81d0ad1137650bce84823e3f01fdf59dc9c4a40798c62f2148833efbef18df7693ad4c126b8af79482a923d3ff14e0f3a8e504
-261 1 322 \\x56c37f414199034b79fdae6dce894d5c4c8d7a56a554dfc980d3c53a756d8e9075e9a2df42c393f857245b338a21d300d055ba70e1bbb8b08455994112f67709
-262 1 84 \\x4ea1d0c5fd957702cabebb4fc229474095090aa15b3c3d49091fcb42dc47c73e577211f6364d084a81f691be7d82e509c5376fe2d54a4491007549287b18c508
-263 1 41 \\x0be3072cc7327115bfa0cba6b0f7ac46928e5a8ee143acad96accc5ab6aaa14a11c1e9f98c34c3cc71af5da489ee570075178a9358f1edcdc23fd297a165270b
-264 1 15 \\x3e5356fdbe95f0148de66dc1b5e5407d27b96625efb551da12fb4b54e8a1bdeb0660bf75e50eb059e2c756279ed822b534b243215c70507409fd78904dcc600e
-265 1 326 \\x7c8f0adf44346899005a06c771b64adcc365dfeedb4dcc392733e9f9b8c4e28224906fd1cd908ac6ef7a9f9693a5992b20bc88b16349916c2756c10896ee0404
-266 1 358 \\xc1cd4aa4cad500d601034da03c55ea9367884676db41f5acb892aa53e2543530b92425cd73fb6142215a32f591e98209a675bfa69a4961c37eb49970d0c4d60a
-267 1 39 \\x59200ff01ff2fff77af746b0bbaacb8b78181b3d9b9eedc84391b1389a8ef2023bce06ceff149bc615a88de36b672e3ccc8c042173ffd3433de08ad7dcbaf109
-268 1 355 \\x0098f6e7f2c3e4cb32bda74b37e1af7a10112a7e6143925a7f58a0add28c9188cfa5f5de00b10dd6675cff2e1963d41c1e9760a2dafc8b9206c9870aed8b900a
-269 1 139 \\xb98788e3bd5a233debd065ebf9de0302325c69b99502fda44df0d97eb159947850905f2210182c6f60e120391af2e15934bef14ac7bf9d09d46347c8b56d7509
-270 1 362 \\xdf1b49a0fdbde4fe65f8f116ed3a5241390d488ec34f3da115a3f9c0ebc26b2bd4e70941b3ba0eae437ccdeca18d8bd497b4772079d37a2a62ae46f4018fae05
-271 1 7 \\x175a7086636e2272d76f9ee386971bd6e81b68921a37987d145fc24fcea03e262da0bfcee8a2899511055e90b5ef43db1b5105f2d2664ad91dffc1078540750c
-272 1 156 \\xd8dbb57a9352798bef3fa5ce93de7becac9cdf3a3e5d776a26ae24ea6c9bda00105637b2e785302ef37f39f8c4c46006d33e71d675e94fca3767850cbf312405
-273 1 58 \\xe9d12ece13c449ccf10c0d2771c461c99c50f9febce1510951826f7b438c2811e555aa6b066c22315e0be61a48f5d3fa782964dd73065fafd2c1a64a95e7be03
-274 1 385 \\x7d1f58419cd1429449f9fa11f845338f5a3cc277e695a5004319766039225e938eecbd748d7928911f16b816a3603dd2ef1175568953e9d7f479808af160cb02
-275 1 85 \\x5fed9a91f6b6540517a5316a82df424476485561bbe9767c6c4369907d8431fc97ffe69d6a9a884f9ae85b00854f21925cf5c431c857ceff1dc363e157e5e104
-276 1 107 \\xdf3c764cdc8e363d89053a87f8b241bc4cb67dc62a7e941bb79dc08fd871774c93abfecff42d810bdd26a203842b47bd8f0002b19a65b3a7ea6252196dcb5d0a
-277 1 204 \\xf549a8e7cfe1f6188ae4887663eb7bf6eebf12134ec1387552014aef337501f044e175e9e7ef4f2d055ca52066f4e8f8850ba046c987b6eabf3adc279cfec207
-278 1 130 \\x0e127afe123e0466c3f0a216f0f004fdafc18245e662c3a9ee01deb4d11bed953932f542cef245a9c9a44310e7c54c7e43aae547c836deb3d4b2d4a781e1ba03
-279 1 227 \\xa43e8e7ef776c219221843f8ff9613dc5fb667d2afe79e0fc1eefedd654f97fe98a54f9c5ebc73a07ad9b1d31ea3f105cae381b799b6d8111ac4534ade5edb0e
-280 1 155 \\x7c1a8741dc9cdb3a161bebfcada7de2f68506c0f49f3a7eb04e26d63a4ab7fb3c697bf112774d25564630dcdf03dd81649f66aa411608f23cc859c03b1d44c0c
-281 1 331 \\xbc268930da44bfd8062764e5d5891e410cde499bdcd8872ad0489e7a7fa61f5e22168ce659e1dc927f45798c49aae91901506fed3f47a9febe2daae430972a07
-282 1 78 \\xe128fb74f95e52dae7d8c47ec7620bc426eb2e5da73b56618815cddce31a907e5e8b3ecb9dba1cb1896be20a5ec91fc4dceaa337ebddf0e845273a9b3e1a3400
-283 1 25 \\x25b9865a766db7143066ece2f4a771106d3f9b945456600a4eded38dc2d79bf7e4bae3ca29695bb10566c14ce5868d818eb49f3679929aba35f6a74821674b09
-284 1 52 \\xb30768d0d6bb2076fe5263ad5a99f0f4540aed204414ca8f9d3fb9bf866b30e9a830c22f4d1d0c9460f7297656ec36ade98436b7dbd46cbfe4cdf97335c87708
-285 1 263 \\x5183ecc45a3fadef0a19037c7800ee3f8f2a7fb5738b73e58a37325ac97a2cad1605fd878917929a733ce7efaafd33117bc50815510d1824dbf08b31cd20af01
-286 1 253 \\x7410acae4327e805e4e97208f7b019e5e9ecb4284b1b3da86bddf5b8528824d0f688d547371390557ef3a3afe5922970143d90b9887f0d56767bd5c97c6b0102
-287 1 197 \\xc5191f20212d3d99b3aecbb79a5be561d42f5f907b897087016f95c3f639dfd191214203cef2033a10c82ca91fbf163a6104e03c1687214147b0680976f12503
-288 1 350 \\x3ee8877ec9cc39f8e3d2e653a21ae2c60b515f2518f63fed0d99ff83d9c53eb789caaf0c7fe99f1306bc43de146a206f7cc09a4875d3f7445973045ef58f4507
-289 1 288 \\xa0864a1de9186c6be5cbb6ebd7c245db7f4afe4c8207e8b2282d4bfa7f2667e582d7924fa1e89e7ac5b2cfb3b8b25c86e856210f04727afc7554e95b8e728100
-290 1 177 \\x10fae9a469386e1165e29386fb0bd20da9338c44b20105867f05962240d0563c375055d77fa01e8bcd59bee5e687ae0ef454c46ce1eb254425281c5ac7b37601
-291 1 207 \\x158bc15a54c99e9515ddbb4bc09f964f8c5c76a83bc58cc8829c92c85d2ccd7dd3a40cf943371a68fcdfd0d46ffdd699efe5869865f72572d86287861b8e960a
-292 1 69 \\x2551634b0fc10ddd585f468da7ac63ab38638fb5690678a68714f9a16fe21533d3f7fe4d62923f476534d2d829c0ed7872129140d07a32d1bede4bb120b1940b
-293 1 81 \\xce513ee6b762c6bbd139247ba2dc11d79264b19a5a491b157c9a0884ced41e7e02eee6863d93805e5b3b76782219936518b913c5b739756004d20e7952272008
-294 1 170 \\x4a289e736549de21743251bd3c86e52d87881c74cd993ab2f9c9422695c00042dd1f67ec5ccd4ac8ccc42621b9701884948b8e4ee57572e1a525885a7e035e0a
-295 1 213 \\x7ea4d7d606a13ec7ad765af3e0bf4c62b2c1899f02d0dc31e675b1f1583e073c33336da2b8206eb140db9d0ea257247df9004987a62388ca5edce24b93769603
-296 1 276 \\xcf2a59f1c0c19a99ac94d6cbe34093e3ebf0ef9fd2142bc9d992fc20a04ec53188c2c72564df487fae8d509b044e66fa16a5266489ceaa633bd7c0883ae2ad02
-297 1 381 \\xc1c7505efdce9042f302331ed2c4b25daa3b1141c7629551a404716cdec46b434ffc91f13bb2ed53dcb4c0013963cfe61a2eff84c1a308d7014ee7f66be36d04
-298 1 308 \\xe0865eb8fb8ae6f14ddf0469bf4977a20eb0f74bfab91f3fac6ae361b609221f53bfb034273a181640d903a65b6b5eb09f48d35c7e46e08836a9f2ddbdef8d0e
-299 1 98 \\x59ecd1692cb73c993c7d57836a372130db5a4ac8a1c71ddb2804dfa7e9161ae4f21c14a1a399ae8d18b4818564e98f6373573529bd3ed96d07fd934707bcf500
-300 1 415 \\xbdd6f15b183fc35efc9ada6152a9fcdb8ed35922c5d5c5414f2adf7edc4e0fd07b4816d032031349f3a9fbe775bc94be693424c6580475d842b387c051e57c0d
-301 1 42 \\x9b20ed31d28c98ee07fe4245a9636d3d1d951a0c9de10c3a703612ff204cad194cc52ba3cdafeb3c5c4b2e93e650ba36e21c6ff4dd34da78f75cabffcb6d740d
-302 1 40 \\xb6eb410ab45211ef8feb76ef005e40f3d85ff1f9c36c5c74bedd09815cf4e7a9f2107fa6528f8bbf33f6e3b9c2669d71164d8f5bc700199c935844f673ec400a
-303 1 61 \\xdd0ada426938b4cd9c3f475a5d585ccdd8eb90508755a625fd01b3a2a526661252d336c68787c908b51b64950246c2ff9d78f34bdcd4748bbabf94673501d70c
-304 1 129 \\x1fcdbfd1513c00b3aeeb8b4a249fdb1e61cee8a4810fb22540e5be9c605ff4cc6e95301f9d3e19a37eb0b6e449b58f83a433e3aef59ac57d07c11dc5581c3b0b
-305 1 117 \\x8982437ed6928e4a90de0e90cdb931de6ff4314dac5efc0061f2a09d75dfd8cea9c3fd78a846d0230fa500201aa52a1513a1ded89123aa185d543e27ee3c2506
-306 1 190 \\x9e36451431fa4bafd0229b395263fd48552c555e9810d76b87c3e2ad161f71ceb8f390f10458bbffef4aa5ff3731c5cc41c4b741afbd656cffb8be328db6cc0d
-307 1 374 \\xe89c927ebaf8c5ec5ee1494545c1a6935298d051ef30038533b891aa75650d336e54e1c29bbde1bbb89610b9263161e3a6854ac5d5d9e2a89485bfcdfb91a307
-308 1 152 \\x34871a9179de116c4d8d4bf3bdbfcdd29c091c6ddbe5e37c53778188d5f8a94e8d0405db01606db3e11592575b6bb539e63174295ff7f8ad64217edb63a6fe02
-309 1 304 \\xe721c659ad95d1fd5d7a37f64ac1ac85858999fd971b74ebe42dfcf0cc3e6aa97a10c5ace08efc478efdca0bb8c04fd494dd951584d248aa0cd2931ee97dfd04
-310 1 250 \\xd3f9dff34ac042b069492ad38cb42b599cb868ab06e4385589cf64f4e56b7d6f95e609caa66f2c9935e32304d8b48f5405d5a59890f3baad7cffd5351bb8550b
-311 1 349 \\xbead1b568f8ba1cabbfcede17f198cf70daf937f19759de7f4954f3277f9b50ec58643fceb6767b82a4136c75a288bf33ed3b418822d055d0dbcb4a3a61ae200
-312 1 286 \\x3847697ab6d863b3bc50a3e6b1f0fd9091e2cf73a292d57af0e261e03697fd26129cbe088ce8c2741b83382b11ab0c5ca14ccdeb3a1dc37d85a6e128b3149205
-313 1 283 \\xcc292822dbdc34a9dd426e7d8bb5f25d7298906e38681727d8508f06240c8a7e6bc2dfe00bd80a838a810bdedaca80ab5fd4d3c1e44ce8c9220758f8fafbd40e
-314 1 56 \\xff101710b2475896bc0b500043455aa6535b0b11e1999d77e65cb0d77ed56e0a3cd41c90e9690a5e50eab1667619e1cea4f245d804da2ab624835f9a9726d704
-315 1 73 \\x3ef643c6edc6d2757afb1bf20bb9c1e27722b9c2333d3561e2c7aee8fcad9d0acbac5a22490c4d845458b6604e99c6b60410dadb9fdc11414662e63c29b4400d
-316 1 166 \\x30c8a97e7b3f529c4885eba9e6b74fae941828856606f6b6e2f67ade3c44d20f8eef5d8b2b837ad6d4bd2859375fdfd29b33ccefa9ad2e85577c29d64b8ce507
-317 1 248 \\x1b640bc73799251f84438420285739391963d5d6df684a5ca08b1b87a35a25621778a4b4479935149d0871bb700808de61156f212ae0b48f9ddfc3292d56ff0b
-318 1 382 \\xc01ab50e167be35d8b83c27125258c0a14a70ec63c48bc018c6736878e53292a6e2053e429314bab08dcae392dd8128861a9147b8ec3a4669ca3bfcafd547e0a
-319 1 24 \\x880bf2deaadc5a3785d2dd7e936f4c66c97388968495ea33256cf59ad0d4874f4601b6f848ad06e501145d29559fac061bf6536f17d897442730cf308bdfee0f
-320 1 50 \\x263b663faa7b7a0ce04651e75fe84f2475d3942df87a4cd03daf011616ce4fbc9178643817899351d70a6648844a88d4caaf4b1f030adfaf37c7c68b41e03d06
-321 1 356 \\xc35a70f031d46d3f27cff1e50089b7522a01dab8c44cc3afaea88587808eab53ba741acd651c36d23f4ac6c777fcd276d82f0cff14809549c294f0fa2e034d0e
-322 1 17 \\x5cc5d78a21372a8a14420ed14881fedd8ddf7e48065cdcc464d9f4807f3fc8fbc444fa7c9a619dc67cb8e4ab3d7bb89cbaa904027f2c6df0c1a456bbc528860a
-323 1 330 \\xca1869b2d1a64959653dfaffc7833e2316f3028e199c97cefa1a226f30bdfdf288c3c0ef029bda5ce98959e0161d368c968b8cebbd620ce909e61c0cb6a39c06
-324 1 310 \\x059a88b70cf7ce61abe661b5b13fb3b1b4979ae220120dac06e3da4c53bd541d4508586a245b13ccbfba95404c411a586743f2acb5110da30a7bc0295d84a90b
-325 1 311 \\x9f8ded531c4b6f4bbd0eaf4435242337dd4049227bd623ae3971416d48754af96745f0cec908cdc856a4b1f7fa38a81b13bfef323caa37b16f298bf474550a07
-326 1 224 \\x0e2c4094d30d45c9319ea726d655771c2c271fd889bfd5e4950fc0aa33dcacdf8e02eb2336df95e185915170040065b14f431bc611d16e33bb1972afc908340d
-327 1 380 \\xc5aa914f28bbf212dabaf1b3e3087f460b3eea20bd9abb64b5293ff3e4dcad5cdca51667640583875fed604fd4b19cf56ec0697e2d725ddb454ef202ed8a9d08
-328 1 172 \\xaffba580a8e92c4140d8f371e6935aba8eab032f1d17347c5e7df8e630a3108bd3cb8ed757c176de232acb5453e9db17727f048680b5782b1ccb3d21e4347a0e
-329 1 262 \\x2c948384f8a9fe011821f364ab977e87f79af4eb5e174f2c29aa6e09ba1db93403cedcdd6759fc9fba6e86ba93bcbdd9f0cc6ed0868bd1c66f3e9a2b6477770d
-330 1 339 \\xce9f848de9e94f9a5503bfce20f6199940052436591c36e7d7a5a06782c8c2be49062d51326d9bc695b3cc896cfb914d74068459f376d549074831d0e6657f04
-331 1 300 \\xf8f8de3a43c9bdf6905d6f277e6bd26f904c55eaaa25b92e19ded3ba78f7a7b5f338614faa81ce97abfb7523a18e30a2925d833016dcd752a7739c9ae90f6a08
-332 1 249 \\x5f957e801791235a44db780b419cb6c6df9964a92ef421821a13eda1ff16f0fbf0758903740478008d7fb66be9dce8ad1d3b0e5d10f3f26ca5dca914248f3d0d
-333 1 277 \\x64121bca38abd1b1901a73c2ffc936f4030389a05c3ccc44e6fb1f456ec73a8da1e3c1410d7ebf0c91ded7f518a93310a8398f8888a81b694616a9673e8f9c00
-334 1 247 \\x99e19b3e11f3bc94940029c0a4aad57ad1cf7bf9a5b2c08331c7f587b1f217024874edaec9c4993ec74c958bf8c42c07e4e52e3c8afd4fbd3364f0b26afbb30d
-335 1 378 \\x6353a92ce0762a574e43125960f3c219ac8476ebe32efb648be7e40399e61abf0d498efe990dade32367ba0d6c8829575f2a95faf362c85821116069795c7508
-336 1 319 \\x5b2b42f051f1e03cae432c4c0295edfa93d405782821f1776fe05859131983ebe06a9e4f0a271aff4b480cd3f70d10b5be8a948c5b2dc7ea1e3eabf1f9e50e07
-337 1 341 \\x89d11fce831514b88fc1b5cff01ce39fc251555f8696879fe88f0fa7daa12cd74a6c7c9775e1f4e4d5a433366fc7dfeadd02c6fab0f5bc2b0a2e5cb496ca7602
-338 1 367 \\x17e36ec7bca9580f5f5522208edbcd6a0c73dce8c8977e68b364387f3e8e399b0225baec81a3a669799daee70f88579c919531fc16b1da19113b49d546f37d06
-339 1 205 \\x646b23a81755c149a427cb1a6daafff270f9ba1f3c3c74e5b78bd12ad66f7d3566d1304f8f7edf210a3f95ee474f82987eda82f355414a5d42ca5317bc1b5207
-340 1 161 \\xa3961043cb743b77fb9078101f3abccd381c14e024152b3034f86b5569750652e420f8f483b5a7bbb2072540c757113675668933df04230f1f14432ef4cf120c
-341 1 305 \\x508a4b66c8ad846fad63e6ded8a562280fc59069b2b4a180773076a6c24d934406bad6dd4aa85cf8d1da067f22a2e85e97ea74eb879a13d3dbb03d6d1a05f204
-342 1 83 \\xf61d84a17d82a67132e89c209f4883270415946593de9470be6c24957967f276911f8f2a0fef2cbd3e3f158b33936ae61fa2393d048c65a6288822493fbc0600
-343 1 151 \\x3e2878867d386577f7abea1b02160e27ae684382b514dbb9bb1ac69581111db84dc79ae50c6d92b0b3ca8d73ac2df166ced4b5c136e10caffb2dec46a53c360e
-344 1 64 \\x75dda22a1d0e14ee32f9047ecc40ec55aed0edfd7b1b9ad0716b584ad5c4419632ceb64e4e3bfa4cb5a0cba2c7c3fc82cb0461b29973782c338c7065181c750a
-345 1 342 \\xdcd290ebe9e1e7d728f2aae4be7a7195586e5cb7010854665381de0b725054baffef677a5b345f0aa1b13b8fadba79ed573dd4e25b40b48cfa1709056b798603
-346 1 90 \\x5e60954aad35f7a3af2041c4aab3c8c5374c91575199493717d3a848105ff1e6e7eb17ab71ada9fa714bbc1eb7cbf6ef5237dd9a506c5d8532768cd4ae79cc00
-347 1 345 \\x58179aec5eab3645714319a4f6bc4279dffe3a54050c22ebc3bf662729ac045fae9cd437ff1e2dccad9c446aac1432eea2f1a412527fa80d5a73296661aa7a03
-348 1 413 \\x5c4ddddb665d334fed1fce412b2dd96b70ba625b6bb971fdd51e022fe9904d3ec86357283676318902e2b9473e007696fea7cc9e79a950bc3eaa587e3a472300
-349 1 138 \\xdd033b3559501a26cca48ec2c90b42366a51284adc0184b1b8c7a2b16fad45de2e53afba2936556d257cb93a53a740f49351ba85e65fa16e1fba2ee1abe9bd07
-350 1 405 \\x7eaf92bfcb72dcc5d943b9ddde647ccffb717196e71ef5937cfd551d3e6e07ce5a7920409d4e1397565cec25499055834a0eb9cbcf0f0cf7fb35b860cc028f06
-351 1 112 \\xa7e414c580e9dc77049235f5e68968eae37f60ee3437fc06e339d828152065ae9b889aef9a90053b9e8dcf55664ea3cb1862e12ee4621d62a58eb9906bd8a00a
-352 1 70 \\x7ef0554283eda237f4f5e16130d52d73cb56208760e0ecf114f65af8f7181546626f32dd2f590898574d15d39b559613916854725b8ef96a4fa9ed04fe5e6405
-353 1 363 \\x1f35bcfdc68ed1e4b0eecb167be1da986c40bc2d0727b7b23b4084a952ec7db98f7e2738c2f0f8b0389a9d7ffdf6a16d94e0bbaf06edfdc35dc3a379f8180704
-354 1 101 \\x5a4c4dc200a00dfe6a8ed7072fd3a7b9026740616887066ec861ce34c1fa67aa97c70716ff269d5150be9bbd54d8e7e9443d4063b8272aa91eb4536a00c8fe0d
-355 1 240 \\x727fc2294090d9419b2b2898f4ed7a380dccd6052346c4641e867e32965339f55b412ad3261bf7545fdb9cb7d72df8655b9679e433675accd474fe79acf6700a
-356 1 287 \\x79f048d8df6aaa415d03c24d7ce289eb30c3878d7485ab59a7fe9e6f62725868d2c0555672bbecc28d5fefe7a8a324c3434a8e8520c1e4243fb6c23cbf1f3a01
-357 1 292 \\x211a8e3b9e25fe2b0a196c409c478899675ba8f86b8240a8521231ac4481bc7a232a7d63ac5336e9f0a84f7514b5b2cc173b4780848635dfa9932208fc747c06
-358 1 74 \\x20c747c67d405d9c4ae6dc5ce1f55bbd184e83aac5de0f1a302903d16cd95a8f3ce5d2cf43502a2d0b02d347230b09a3ed9481b1759f0a406572974af7c2110b
-359 1 379 \\x02d18b6e94853f07a9c2bbc35c7ee74f2f7ffd04616cd805f57b4ad035131771979e3e0a1bc6a6849741c01943745d7bec297335215fd27c1a2b323271a53e0d
-360 1 135 \\xe3f835c28736509aa6e5c6d074e9f69ff09203e15902631cdafc0c8f349658a2a0439d41b6cc26ea5c41f7c19189d1d2b7bfe753f081638eaf61e96247208907
-361 1 268 \\x5c961347cc785b7e9fa4f27992c8acf6f3f67ace583408fad2451a5cf98e8c9c6664c72b583b83d276f279a3fa548d79244f12d1a9d7f31df4623183fca6bb0e
-362 1 392 \\x6d0f6f7a889cc178942d09b4e4071877695e5b05c9fee9776ca6b374d7c52bc76579e753b6efc4307cf3003f404ad04db1bc4fc4d6c6a099851215f1b8dee301
-363 1 346 \\xa834f646d8f5b492af2c1c279256d116e8b3bd0087970e41e662d8e8d28e26c88405c14820110703a3bfe49e20831c41ca9dd24bfcd0fc9bcac1bf6695302c0e
-364 1 389 \\x47c188f16fef15ff6f7c1d359607e42d64717b366e22aaf2ec26da610c7acc83e0138345b79b903c0296316cfa6e20e0923ec99c71cb589a91cb44d0ffe65903
-365 1 18 \\x6e360adfe5b9eb33d6ac2a9357e4e74e8e0d69847e2343a7bbeade6469d6e22ffaac57f28286d5f2f25510f4680cd38ef9a983bd79a7a975ddaa2119cdd2f309
-366 1 353 \\x10da70ced6262199cd40fb558ea676346af7e76d660e987c443ade0b49553fa7c38b0b820c80896199c9ee67c4a6ec2a5264c3d9b69b42378548e5319dbf5a0a
-367 1 131 \\x7328935fea7e8d70e39cf5cbbc2b2353ce2d77e727e451ee2763d16dab8d9c48277e52856c57a0010eb683525edbe625bb1ae6e7a9e3e0969991b8af1ec9c803
-368 1 120 \\x12330ac296e44638838b0acb00604964652206bf59197ec3a26f15d5785b380ca74399037abe403cac1b513b1696062018ac0c4dc331b75b71ed26470d06030b
-369 1 400 \\x8a9075b5310d24c7b5551239cdadb31bba0580e5f8f886d054b8678194a316a6f6aea311eef85096f81f5a6630868274739ae0f3dfa71ab79a4998f4d7acd004
-370 1 260 \\xf0b8331aa7ad6830504535aacbb833f98f9773f41b6035651f656e055019b2c92f294554372f02f95057c9f9d9b7d6f4a43a618033a8164c715d61901f95da02
-371 1 143 \\x33337792dfcec5256d2ab25ade466eab57a317d01078cd380d6276ba3716cb6e539f56fd415534c211be8ec048732820bb8bb17083362b262e64da8231bc1a0b
-372 1 348 \\x66528e0d33be3677117797b1467bf0559d3ca3d5a12a59bcd8e494f1889d21190c0aca7d3b216166dbcaaba093ab43f3ee74acce5ca8d0f6d4a4d924300e080d
-373 1 36 \\x4895bdba53b7374da5c7455b30f96c0b7db0b0cc2c8f79cb1323a44d6e8030c83071a686d0234a1ef40e842381b53af7da5ac04609109c7c3826c0439386d603
-374 1 168 \\x770c01859709684850f4c45bfa92031c7b9e0a4b7a4bb7b79d2b9f3af063d8db18611544568f46d6daba6e92cdc75854e38ee8c7f02c6c8a74d34acfc190d80e
-375 1 418 \\x1d39f884ff60df3ee1119d1e12a289c3876f5b1e52ae0d67ef160c1707b4cd5631275347a1e672b4898634bcea682e9910da5ccd12ba7d93bc9b4377234f1408
-376 1 187 \\x6c25839743d983963df550352914bb8669ffe5ce99306883dfc3c7ce68f450bd5884f91611cc65f4cb1250ca5667499cb2e23b13e9dea862a61d17db6cd5350a
-377 1 368 \\xce6f84623ae2a382f9d71368f3fa32e10ae0abd7719073c9584ac48054d5940f205577291be2103699b496940a23f7630a2ebd094907e9378affdf5b0eb05407
-378 1 324 \\x23d05bb3a7c6ed718ce505f0605289869c98366b19fd48756ad14440be05bb50b89577021d3fead9c4ffd2fa8289f7e89310d0b6a287f946a7ab41a86f4d2302
-379 1 303 \\xcbbaf123b127c4cb934cd8455191811f102ed7347c29148fe8d9445916ed9b1a1f2c5045ae14942bf4385dd2b982eb0cea460a9370145261bdce0599500ffb0c
-380 1 361 \\xb31a636fdafe7c8feb8f5c1797c67053f71f7c7e708644c99da5a3c45bd3d1871d9c1ba419a0110ad39ea49ebe65eef7d32fd83cc19a047cbfb735f6a618580c
-381 1 237 \\x8661dde7307026072e004a33a9b5aa19a25423068df2ab1233b45bc69caca30f49b6d7654bc33f7eb8245423e5aff42db5d7428b4c46b145dfa7c154e9fb9e0b
-382 1 317 \\xfa0cedb44fed4afe1c6206137fad72a6aaf64edeaefa2f7696a1033bec4550a8522652a1e214619506195d6442aa861c3bc4962191c881fa078e48e1cff30800
-383 1 352 \\x3a731c88079e34eb3a64c111a107fb52229e7c482207c51536b2b2c4cc033d2d20f975e61f6ac5f5b8fa1876204bd58230ae0fbd3ac3e6c0ccff5f608837a109
-384 1 257 \\x1e46f9496e1910cb0217b608ca117e6b92186d5feb1dbdada4ed3745ff49f21bca3b5933bede1fa0aa0f2ee024b93643bfdf084ebab1e1c4501438a9acfaf003
-385 1 410 \\x59f9196376ec1ef946cee44defc1cafedc00a8168a3853eeff0b8b4642720c727cbe693dc09046dabdd6495fc20c58cee56794b4b608d45030581a1525929503
-386 1 316 \\x464e469c936414f16e812de9421c5192ae6b141f53768ab696eb54223910398f3d23676b32594ae6066e368c5f32bb94182c0ab30e2edf7b8c2108a341e2a407
-387 1 20 \\xc25d71f3657a27a086b78359d6e3aee2ccc844b7d72ccaf25da97e75b1d7611c376ec1a814a299741b79cde54a3543582d8f16190e79febe894d826568b1d805
-388 1 223 \\x5d983ecb0cb9490d326acb64dc7471a260581af8cf4e076e6d06e79dd4a034374a79fb7fad84ee82df83c43e2e2e7708375311e902676aa731c3e4f69ac2030c
-389 1 72 \\xc4e18b0d9ea83ba03e8aa5dde295734433c9297d26a1604349c7c168dbc11e5c14757f992cec83a461fe095cb61efa979db590c4c8e5b9e71a8962823e2e0408
-390 1 167 \\x0fbfb024ccfb06ade3195ea7020eae9accfd276804800f170bd36df8f0bed137e82bad874d8ae0818116942fab9f1bd8be6168d0c1a3898b1722de9e80614f00
-391 1 121 \\x888cb0afdccc296646a05667b4af8dc78693f87113e01e1b20ae72a8bf0029c46776feb01211ca25fc9529aa67d2c0a19524b65be09f67575dd96843fad83501
-392 1 199 \\x78f5e5cf9bcfd96e058a809265a7a8397d3c8597f0200983b203b36f15a35cde0548ffbcaf4274641c065476c324ca05956c015b172590eab181d8db003db609
-393 1 421 \\xbdc3243d5b06f176f505e5cda157fe941c796eac774f5c97c0314c8ed6fa4b957bc47f257e409a38416ff500eec8f5419e7187f6c7a6ee39e117f987eadddf06
-394 1 67 \\xd62e3cca27f6610858132cc90cb25bf6398cdb687619edfc59ce1caf801d2ff95d2b50458c181cb46a27708ec40b5ad4647e7fa4ba4144fa1d491335aa3ca70d
-395 1 407 \\x58cae00a41ae051b444b83ad44807574b2321eee373c304e6f3c2c9947c0a115335134a726ef04cb2b224ad4597739d171594ba0c6171947a54cf5301a1c120c
-396 1 33 \\x4fa097aac72e66e7152499dc757417c6734b56fa7a052af9b3225d8ce46b1c6e84c981f1c08ae1c6b0fb0efe999b066804e14b2b0932dba6d0df4f206d97db0a
-397 1 261 \\xc57a2e8502b2564d60d5d3c5f2d2902b3cc3654a061f2b2ad7982bc1362c7e23ab5f6ac95d955d9ffd3eef232064549711b0b20819a945d96554fb4417470f07
-398 1 296 \\xdf7ff7775af92992afcfe9692d6c877c6ebb92b12aa158d1a27e0f9b05e6a9f6e6269b80ba4c6dc38d3956f713066889255ab18685ecf69210142435348af80e
-399 1 301 \\x8d2cbf8bff48d77d0d471f0256519ca710335efc484fcd8d48a025232f09372ae0b26c106d7fd0a47efe27ed9f1f5673fa12bfe38cc85c2d38271f03318d6300
-400 1 321 \\x4d51d446f473e14275e477acc55a6265b17c1a4b12bb3f288175217642a2b92ddbca7d9ecb4cfa871e27eb374c9997f1485e47f1ec55a531c013955eb9c9ff0a
-401 1 49 \\x024a2ebfa5642d3043d267ffadc5b4cde09c9104e4f6364c8697a7b458278ffa609fb303aa1b6e6771f66184f0ff82d0a6a3ae96635ffd630711036dc8f90509
-402 1 369 \\xb7a368dfae0d3db033e950d254cf2a0114a0f26b8d5118db25523c624a4fe4f8f12b92452a8f7a1725533715c298444e90703ab68fdeaea8892aebf2536d4b04
-403 1 191 \\x77385192804ec26396e7d4a36a514fd753fb36dc8998a639efcdc8cbb429e8be8288fad5f9b9c564dc50adb17e4b8587dc25b21bf2bae98b219696047a6b4a0d
-404 1 215 \\x8f7606348545fc3a9d31d34a0f75c563f9ade07f8835b48fe6fb7348fac7e63d825289da24cb1eabd68616f07f07fc04ad96e4754dc820053b037ad22c73770f
-405 1 113 \\xdf710fe04e529b0e6ab58290a7e7b7827d25b219dabefd46bb370dd0ed5507124412d2b29c24f329339b17945e131b27fdafdb734d28b594b40d6c8465c5d60c
-406 1 412 \\xbad930c676f2ca2a1a0872083fffd0054eef8034fb21491337f7f2a20e6f3c05dd433993135c70b4bdd5be4c3ad63e6a2fb6e05eead19fd52d76475882218b0a
-407 1 241 \\x4b4a7952823f2b543ec9d87e46046f8416614a99b095e39a97127458b93a77708e160e3ca8208da68392bfdb69babed3cc35ccf1a03d54312c338fd389c91d08
-408 1 32 \\xbe1b79e5c541c8ec12bd5ed8286a56cec84d433974144854f992d73dba52be0a95947e860830a02984f32a35e014a2e2cbb5d4aa154d1d4e074ad7920adc3b0a
-409 1 264 \\x6df83cb7d384fd745c35035668d59382e4fb3b1469d5d1eb2443b2a3c45ace71c5532532d72618570a34b83c923d5ef22099508fe79b6ce7c7ba19f23f2bb107
-410 1 242 \\x3ef9e17d6535e2979a89c7f0ca88c928149085e0cc226bb1732c756443b6070dee0507efd7effc5828e8c6dac4c499f87cc85a66a0c8f4849e85bfc65f08bd0f
-411 1 145 \\xff5ea3eb749aea27e582edc66a033de8b3f149283b824366186d413f38c7febeb43d8ce0d98598497767e48adc2a90418c501a188a92110ae5e09c0c655fda0e
-412 1 124 \\x36fc2e28137b930d192d92421c285966a0d9ec991cab970987a6404dabca97998500b7674cb4f2f27fd1b3846e3ad35b45821de05c702fc78840032275f6d00b
-413 1 364 \\x78f99cfd84867ea0ff43efef48433c4bdd1337ef935ed4f6194d3da31908aadb584e392276ed405810813e6b17e28c4ae51c3335f9951e20adcf876aee577e0a
-414 1 182 \\xafcee604b345790296b3c5b12dd42bce328981295aa7705574f934faeee5a21565e7cb162e946c2af241b800e00e653130051904bee914230cf96a38d5f8700d
-415 1 62 \\xaa3a7ffcd7af86d8d5d5e01aa44fbd906101e337c2ddd8159af3a6a2e63c7a3826f2e2f5dee14f6c8bf42c648098cec5e26b0c9e34c10ea1a8090ea3420d1807
-416 1 406 \\x13596ed5360cfd68c8cf04ab6a210b1cf46e4bd8097eef5f078ddf7a6ec7930d3ed7994cbf41dc90f1b99f7f78e2d711c9e0f20831a722713271f00513873503
-417 1 165 \\x239f0ba1997db8e5300f882593196d78f49cf10f4df733cdbb91dc499156989f238be1ae955129002b4b01fb45a30bdbe8ca5cb18a0bc1ad0e188f3f090bea0e
-418 1 110 \\x2001554e9f5b9026bff99ef3316eca9cb9af519f2755fb5fbce4f508d9b42377e2c2c5636a409f0fff73e988e020e2d0fc28fac581f331bc0a96a2445d0dd206
-419 1 202 \\xa9df4fc3f6c687ac80cfcf592470b47d9427a20617b89aa068a12a36ff95ff3670aa42a97a0a495f2a6ed9fd8037556a40c39e65533816edcd82b41a53fc3d0d
-420 1 134 \\x6b638cd1d3a937f5bca1cbcafc4a9fb446fb48929368a8abd61a6a309deb6d4287e98e76fa04debe6adde4bb483f0bc0e16a5d3110e0b0a98a48a29a26c75706
-421 1 359 \\xdc1d2e84fd686c7ab362abdd78ab96386133d8927474133df1bc94e09e90d619cc8be61a48bcd1385b9b1e386ef590970cec4a2423a9d8e3504fc92161fa840b
-422 1 92 \\xb74fc1fe3ebbee2677f3e79f734840ee0b6e5601948f1845bb081e3cea2ff1fe3b255370bc4ff49ff9e0a7d6e172cde416b189cd797f10554a156fc7eb792b0e
-423 1 93 \\x615ed7fc0254297454b8ca96e839d1a8bc0c825e3a39826283ba86cb874c6cc9afc965efa499d05e6a2dc4a02778616207c612a959acfcb608d40537df56fa0f
-424 1 297 \\xd3176bf5300ed1f51d5b94bd8dae296fcd8571f0de8a8058a0ff06e20350679c77a0fc9b8cbd7fcccb3d5608fbe01b6b9ea3858a29897ee90a85e7e02f83ea08
-\.
-
-
---
--- 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_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;
-\\x48ed82513cc149c010ca237982a36246850491b26723e0568be0b28a9b8602b4 1638366941000000 1645624541000000 1648043741000000 \\x7381fdf0de425ba7c87466c78b506c9f453784258154f22784ba2f4159d1d267 \\xa0d298ebc68805af02cb0f994d48b4c7cf88c9356a9ce46ccd55bee9053fc558462f46d171acb343b405e45d7429ab1a3fcfeef76f4269cfda2a88b656059a0e
-\.
-
-
---
--- Data for Name: auditor_exchanges; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditor_exchanges (master_pub, exchange_url) FROM stdin;
-\\x48ed82513cc149c010ca237982a36246850491b26723e0568be0b28a9b8602b4 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: auditors; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.auditors (auditor_uuid, auditor_pub, auditor_name, auditor_url, is_active, last_change) FROM stdin;
-1 \\x576f2a47a31d4e2ce025ad523688fd4b3b519501ed2679fdb11a7ff6771c20e0 TESTKUDOS Auditor http://localhost:8083/ t 1638366947000000
-\.
-
-
---
--- 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$260000$p8IvxGXlI3Db1VVTyG7Vyq$2KEQt1stbDu1X3D79yecHZjnbUOEeHfZ+vwBtcUulqY= \N f Bank f t 2021-12-01 14:55:41.623585+01
-3 pbkdf2_sha256$260000$yF1NiR4UTNIGjkfvG1UDuw$towB/2xL507PX5ghmSpfrXutJL0n5/8E22/8OUXPBmU= \N f blog f t 2021-12-01 14:55:41.857362+01
-4 pbkdf2_sha256$260000$5R90jy6hupR5cSPfMrKqNL$EWpTMoaNUGDvI0WegMUAZZTY/dowVh2BcBO4pNxypF4= \N f Tor f t 2021-12-01 14:55:41.978251+01
-5 pbkdf2_sha256$260000$MtJImAJeww98TPE2xhIzpY$m/CMXm56Ce2Y5M/TEzhV6Cl4WIt9+eV0dYWC1po0hWQ= \N f GNUnet f t 2021-12-01 14:55:42.097013+01
-6 pbkdf2_sha256$260000$qjxl7r3wmzhRqSrh3ACxXZ$wc0OeNX4KMNZyIs8O/H+tvvZw9M+22KrfGrFjKx6Om0= \N f Taler f t 2021-12-01 14:55:42.215284+01
-7 pbkdf2_sha256$260000$qZG0Px533rLBAARDMM2FxE$/SIsjDFZoDoRol1SjIBXwLlIlme1/buF8rcWergFlWg= \N f FSF f t 2021-12-01 14:55:42.333893+01
-8 pbkdf2_sha256$260000$L7tS5iidAArRrdm27XbRuI$Ydd/Fsa3PcPyXupJ13qZFRLaLAjDlt4xF2nM5YYvXm4= \N f Tutorial f t 2021-12-01 14:55:42.451999+01
-9 pbkdf2_sha256$260000$PMTplYqYRP5wkOB4ydaSeM$3+9PRHbh0PdNUUEUbX7Eq96c926u8ydW7aH2xkpSouI= \N f Survey f t 2021-12-01 14:55:42.570292+01
-10 pbkdf2_sha256$260000$qXqwLgJfIib4ZcV3kDnUxp$eocv9iVMjEDqv4CquggoNyNsrzhury2Hl7YcxQzePYE= \N f 42 f t 2021-12-01 14:55:42.996536+01
-11 pbkdf2_sha256$260000$g1Yz4ZYzXZRbR0nQJJWpBi$585+1ZlMuEWE2zGo3g7H1SunHmLLGf8jcgkjM4VpA3M= \N f 43 f t 2021-12-01 14:55:43.409847+01
-2 pbkdf2_sha256$260000$rFDsOrNuVuyUpskCM1QazZ$Li+E83r3dEP1ndBw0HVCyDj1soHaGR90MBcyFPizIG0= \N f Exchange f t 2021-12-01 14:55:41.739521+01
-12 pbkdf2_sha256$260000$AUcLX5ofTYdzstRx8D4O6t$K2pRyhaleeD4FBiyFLVFzn5CM4AWTB2y0kLo6pDWNXA= \N f testuser-kusufpjk f t 2021-12-01 14:55:50.894182+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, denominations_serial, master_sig) FROM stdin;
-1 92 \\x5c3f2f70de52a95881e5c4ebed1b6c20bd74473c25a1a595d89e0e1924e40184d688b2e0c2d1e96fee291fdbf03600864c44749709105748a2658776bd2d5305
-2 364 \\xd64c7af36d9a35591c0d3dc714867abee7a5756f168819c1d188edceb215edaa13c48ef5cdb8e80f87dd1fa0ac9546026d8b5083b0e1c95e7a9f3fcb4a3cde02
-\.
-
-
---
--- Data for Name: denominations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.denominations (denominations_serial, denom_pub_hash, denom_type, age_restrictions, denom_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;
-1 \\x008cbcc9c5a4925fed43da4392462d7cec0069a2a94ff61d6c8063356407cd8afc1bd9bc7b2c649f4e81863a9d1e01a12c41c09217dee36ccc8bb6bd32db8923 1 0 \\x000000010000000000800003960cb41dc694644dc0ee6fe64e4ea917bfaa131d6325694d366ad52448d82d10e6c80dea631768534720411770f19eb2a22dfbd77c83c4760db5f6cbf4f18f4d94f9c05a4d083618a720b43d56efd6b7b63b5d7134378e79c30f8ed76b4355ba0321dea88efabccd424f980a183447251fe8c04c9a261f73733ac958864932db010001 \\xdca5b1be97fb9a555a2a8aa318297b9b84cbbb2f4a25ad73b0be52e1e4ff29de2ed344b34f8e9244a571a7bc0181f31d5e4bc81a509adab092310420314d6005 1665569441000000 1666174241000000 1729246241000000 1823854241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-2 \\x0028b6af29af75fd4d44e981b0ab522189ddeca047cbd282bab7d4b748962fd54541065a3d0d62eef10f761f7f16a6a34bf3c6c662e28a88090aad3969c106a3 1 0 \\x000000010000000000800003e141f513ecd08b032cfdc2983e099276e5e8ec829df5acef61b2cbc600fe39dc9ae6e679cc54459ddbd9b1b38b7c537240e4705f07c5cb14c6a95ff440c97b7e9cec6eab81432286b2b2a040462e4186e48f486260b48977336e8914d638026bba30da28868db34b0579c125765b4155bed260faaa39d5e9ed74969ccdaea679010001 \\x6395b4825e2ee70263fe7167cca5513d5c75476d97cd0b31a820f0918c43c83dc627cd24409586f6c16b6d5b56d41f599c6560a97df00d193f63ed11d32e9301 1652270441000000 1652875241000000 1715947241000000 1810555241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-3 \\x060815dff10c515d55b8845d38eb0eed8de522615a905c15970ec9784e2cd370b02bce72bb3b66b1c692cb5975ac64d02497b3800adfdb4791a68962dfb430fc 1 0 \\x000000010000000000800003c04e2b8e8461d1b432748990830d4318940af7844431267fef9f249852efafb8b2562071f0eabf83d2e18fde4a63bb32c6942129e256ca9ffdc98978a4c7e9b08af4c5a515297d615155381a0dd247ddeeaea6c1092d1776b4377c7d98df55f52fd1a775c0940f80b0d09172b535ae4aa6a30473e682b711e10aecb8878cb98f010001 \\x5a02a7fea4ac1455afe92227344a565706db1f26b3d2bd107b79caa8b6562db1bd4c1a48a7fdfde2c448439e2eba6bd53f656e06fa9994eb4acb7bfa5f569308 1657710941000000 1658315741000000 1721387741000000 1815995741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-4 \\x0ca4aa776849c4f6c506b34dd5490dcc39fcebca41b1b19aff2ccc8e1e11a1af1c3f0f80f6e88ab50fc1bffbc8b603b2d55e406ded3f730645f6bad140fc0478 1 0 \\x000000010000000000800003b053a1e714ade9a7eb088ec79c18aa06587e5f985babf2d2148e79aad969d46abcc7e5aa9810920d6c98db68482f8ef3d805f75c99b6b7888f7ffb9ad63d99ddfa381b380457e425704b28e83334a5c991ea2b47bbff939ec26ed8feaed3f1b55581895bbdfdc7794cdde1c78987e8530ddfab15d164829f1c1e071c54f11c15010001 \\xcc9c13bda710ba7702d4964ac7b5de6503f3a69f16cc37e330a72f54e51867b88ca0a98e65272cf52a8838716317e2455644b291d90d4b3d8b5032f89452cc04 1664360441000000 1664965241000000 1728037241000000 1822645241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-5 \\x0cf8a5da00817a93fda79e90dff92d5d652a423f08d5f19ebdeb7b455558348854fb0e912f1bc48d11efbd077a509dfd6b79d2e46b748b8b1ddebc520d83c9de 1 0 \\x000000010000000000800003d9acfd05f3d3bc3ea25ad57b5d80eed8334fd440b85e1b99f57f7dd574e208e9cea66ce0162a71d622c23396cfbbd22cdd1abafc98ce8ee1587a8f7134ffbc10218b7f984375b97d67a041fd1f4f007c8a7eec34c570f7c7310cac0566308bf50263676c31eb84c3a2dbde92b17b051a920f81c6bf546a7aaadc8f39176d7c5f010001 \\x6a80924ca82bc097ddee4c0c5821d02223b76e344120b9fa511b778b4001e0efc424170af4d331950835bec214bd05498011b603d00c0b32609a04f5a5f2a900 1653479441000000 1654084241000000 1717156241000000 1811764241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-6 \\x0e68813a589837f2338ed9b0c8b25131c9e64d3ac86e2c88401699ee2a5811995d138cb5d80a36399f3da600f801cb7f2a1e5a3c46da382e3452d3e7e8556d6a 1 0 \\x000000010000000000800003b5b04be30743ac2b853a592fb4eaf45da64f05b2638b1e856f2a47eba564c1e29a8005527b0606e273e42efa9f30c96fb1d49f4974241bac59ea5599bc55f4fa43a5a98bfdd5e6f6c926253e978bdf072dbf11fbe6a69af9f3d9a36f5ddd94e139177bd0bb824969450d8d139268c4b3eb8ab02df60f79af27c28e61d99db6e3010001 \\x15aa10007b350f5f53d1c1be96eb9bac7c285733995cc0e79914153096df33a671df6898f29def2958655fe6ebaf0d8a79c855ee3bfba7fb3cb7b4c35ae02e0f 1664360441000000 1664965241000000 1728037241000000 1822645241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-7 \\x158c52802f9a182dfcfa305ff9424857856608a4a3b7949439ddc8c3cae4e931cec831f8cb897d877210f7c01c2a712f55511472a5d612b2e9552867f186216e 1 0 \\x000000010000000000800003c1885b4f7a5fa6ae8dc3d8038956ea09a2ea999ccab206a35e34be8ce06daa2b490c8df103b6c50ed384d3ae8872cbf2467c991e41339ea0e2c33f8d72f037f012580b90930d4ba25d432ed36d3e8457f3021dd0d1a498ade519f32f8fe14515b51b1e2ddb7bcd6232da16656aa92cd32f5232dff47b7395ed47f7a30a1d879f010001 \\x48284e90142a31d159b8a1c98c80c6ba6a91573ffd883de3f6c26a1ab1473fef155073cbe7f407b1f3d541b9c6d238afb2d2ca162e9bab1d3e7ef5db26234709 1649852441000000 1650457241000000 1713529241000000 1808137241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-8 \\x1864804f8da104c6fd3172d66370aaed23127702cef92f3241af429c344f99c26b1622176f1ae63cf2d1a64394fc7d3431383da2aeb67fa4274ecbfd36108889 1 0 \\x000000010000000000800003ca878d485022bd843d8c447fa0201cba02138307a20cde1a3b408528777ca3e15a2f1fa2dd7697112d60721da6514e76d6f9396b09aec27f2fd519b2211d5071fd8144d31518a81532ad7215e194c7737c4ac5ac16dc99a69224a5c6f548eaffc371ea2b2203b3c61be2d463326747b6f31fa77d73eba72082624d8c610d80b3010001 \\x2a1442dc377e0dadb37345d5e4e93d4dc1d1e50b82ea68c615eda9d5fa49b6e273d630d8414b7e811f47e51f7d598aabef434f2b70c08d34456671471baaa808 1655897441000000 1656502241000000 1719574241000000 1814182241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-9 \\x22f4b06fac57f442f204e897a5b27b5328491d25608907651c753ea9aa44b0baa65314fb46ebd457a5350c114011280970ef76af28c461ea581477a341988933 1 0 \\x000000010000000000800003cd814385afdbc2095f30897c6faa76b5aeb2d1e7e174ebd1b874c9c01617f9e3b0a4a98b29b0f30035b9117df783e34de3223b02840efb962f998a1a227bdbdafa7c7cd5baa9da84e29aef0556ce39df6a79ab8e4222f829af6503571177d6a41b68f61f7663f31ba0eb621a1c12b0edc4b47d14f2088b35f818e6450fc58de3010001 \\xd2999153faca4168b9e7a13909f49b33b9abe5a46012b3095ac16a49f0441d92f0fbd92cebb8da123b3493fa62ae769b618ab76442639b686ac5bf7f6b9fd201 1664964941000000 1665569741000000 1728641741000000 1823249741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-10 \\x25a818f7e3fd43a29870bb2afd4e4f38e73c9f742d76e80654f77386f9af9349994f92e3323ecadb3740010e2c77e9e57925af2af6dc9f856770c2cb14966ece 1 0 \\x000000010000000000800003bbf003282dc6c0e9f8712bcf34b24dfb44339ae8c48a734799f2d1881ac17646e744af9831b8c77e9ed1348a31663f82d86328ab4f0a7de611f4a2e7ae0448177ad40c06984d09e21b483be7f005f4622ddfa2bcd06cc7eb50b261230c5cf31b89b59e406f834360ab2bab8e1c23f88699013ee561c99931a0ebf44bc0e451c3010001 \\x6d18633c34e23a77ad822bbed1d88a3935723742e84ab75619abdfe43a5ea07e89988f19cb59f54f3a62f147c3e2d02dd69020ce7fb4f61ad99dc5123ddaf20e 1663151441000000 1663756241000000 1726828241000000 1821436241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-11 \\x2b244f36fb40aa1d05caa06a14468b3748d102711b850e6de7d27558409448d5318b70bb87ded1eb2361ca4513e10e33fdc0054d5de1bd1e3afd3c528916a80e 1 0 \\x000000010000000000800003b8cd2f498b452fecc223a26a75dd42a9ae924e373283b67c811812de4d5ee2269be49e3ba220aa5c24a8bfb10717762333956dd82755609f8ff06246417fd42bbb37aa83dbb39c513e88e438123af729f23035c9ec7af637897e9e9a76f571ecc4024496b868a2f1ac100afd0c64edac4fa449a16bbcef89ab502527ae642dfb010001 \\x147ac8f69f06c610b7c7f7b5e4e7cd4d920637c6d83fa1f290a4c95b93b19fe96e9ea77dbbeabd0a568ffb39ccc4a468b6eee6808256d7acb4600bb3b4f15703 1655292941000000 1655897741000000 1718969741000000 1813577741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-12 \\x2b58974c5cfe40d5b3fe67da01ab7bd5a35672c7385eb37d58a1e50afb03eca8a0d581aa801c9a42f29d8f78b55f0a13a0b8330dd5e8f2dda4cd5b42fcd98d70 1 0 \\x000000010000000000800003c493d4fb070eb8b38b7a492cae15f9aeff0da5170c3c4488a859b7d03cd235c8447a610b04210c92aa3dd7da6efdf510b1bbb7873ef6554f42dfef31b5aa7ae117e964f54af99b32aec19f229d5641098d801d32baf10602d728691ea571f5482d8bc3d7db90a15e52bdc97436fbcd641ba5b24f33cf417a04e948ba7783123b010001 \\xd637b6460f29c42e4b7932d60f9e010d913991e1820093602e4fa510c2b4389cd20cff41ab620ec7db1792083a6b0ab536a7d5ef1130332cba774af26a9ee30c 1656501941000000 1657106741000000 1720178741000000 1814786741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-13 \\x2cc0a7ebbf1283573f1276dc51060232509746871f430e4ad8d15a77cdd5e1ea049006076da5e20d767f062fb66d3f53d958c9f741fda39354a6e29fa851734e 1 0 \\x000000010000000000800003d7f4253c3aa856dbf85a72ae4e2151f106bcaebd02276e949a12d3d3ee0aec3f3790c8c3b1600cadd76d0e311b89e297d4aaad6d41610e73a1cfa27c7cc1ac53f83503ec09ad62d2ea0b13878167b2489ffdce755e2f2c6e13cb1e16b755b90cc2d3a95e491b4e9cc9c604b09319ebac0573999ce1c99c3b5a081e0f17bcf8b9010001 \\xfc078657d1b265359d7af983682c30e96ff50eb4a8f3ca3739ef904d1d1af43f56b8fcf3248a2848faf264318bcfa3a2b1abbfbd59c29585e88d5a4e795d9e04 1658315441000000 1658920241000000 1721992241000000 1816600241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-14 \\x2d707885494de727120df12599e68d211ad1573e92a40bb1b34d35e931681301b1faaf33612a652ec14891b9d7a4d25c6be3f69ce9f43441c7a4f619da46f43c 1 0 \\x000000010000000000800003ac6896c2e714e60795e19e88586f3858af62140e6e27ff90ea14cd17add2fb614a6f4d13fd49cc2554b13a3308a14dc6ad80aa54d1a47c62e47a4c79748940e5eab3a76841a5eeacbbc6d106348d139607b34afc1567dff2ac6a7f8cd472f52a8193b7098056bae5a52a0995883a6d4f1a8f1c079f1f824cf6af652b052924f9010001 \\xa04ccd78a0bb8918a65cf4fbb4fc9cdb7b24b6790365a7f2a885a4afee47463ec3579ab2ae0efdea4570eac10af008ecfab60d8106aed6d2979609d6b313c50c 1662546941000000 1663151741000000 1726223741000000 1820831741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-15 \\x3348c1b38092acd120ba4246bb378f969a1322a1542363daaddeabf5c9c72009e73a359d5b0d362facac9d4eb4f803fa7fea66c0559e5e6b8c88ec5bcb625c6b 1 0 \\x000000010000000000800003aa094b7eeef18ab5d77b90a1f530065f0a04ea452c88967756946d1582f432a3220e8236e24ad941073a044171d44ae809eaf4b3ce1f8201017132357cfcdbc09e2422af64dcfc702d2e87e44c22553a861984d59a590b833dcf7f104fb7703fd39b05b1718ca50ed2c2a5b150f793d79b9b7608a16860a3fdb860122a246cd1010001 \\x6940ff7e5cd8dc0b9f7313191511bb26833b406ea2e9fbfb1925490d10218e76b00bc4df6e2ea7b87317d457ae3387e9d3b5f7c98f7709ee8806b8901e630a06 1650456941000000 1651061741000000 1714133741000000 1808741741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-16 \\x355052a2634e183b1cac1cad421b3596e509dcfe5c6b6c3a4ec51231d96e3a35a35c94b35a78497404cf4c0bd678c6bc705f40b785cb5e41b6bb98fe25daa3c2 1 0 \\x000000010000000000800003e3d8491a92800d7de96389482fc1b91a13ef70b1e85c415c09d522c6b33aa9f78e0359e41394c6c84b0f9f7ebb43c6a22806c79968ee1891ee730e52481e04a48f1320f547224281dfafc118e1354d985a15fcdfd1242b40f942dfa5949a4279c189a2f598d30a68daa0e03fafafff37139cb9906bb618fe582b55f5c1732a3d010001 \\x4bdb5e5e8b714b440b3b67225b8dc8facf60dad8e818936d6acdc68a0ae462a4b346bf7af5f47b2610834ada16e1a9ee391b12d8f84c387cd8ec7dcb6922c70d 1659524441000000 1660129241000000 1723201241000000 1817809241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-17 \\x3a3867177bfc3b2ccbb5923183e5fbebe0bde8643542e1415f604303ebe928bdfde33ef3cdd0f4c821bec2982b775bf3b9e0e83be4562e092790c5698b149a26 1 0 \\x000000010000000000800003eccbba620fb1a4dc321a421ba19bf97b036dc0f7fc894a9797b758238dadbe550781f230f8e008f1995b22ba37744837eb7274c8f077479ff29db6dd8c0f01d3e92935fbf1c42c0cfac17a11fd45cfa539a854cb66f8a8870a13c790a4f6082ca90192ab5f73b75f901a0f4ba0c7e5c581dcdf63f54ec3befb580b0b0fbf7285010001 \\xc96af5302a0b98e43c8469ff824103febad36ffbe43366e66e9362abb7c57a9dc481d065fc32cc273624d35832e967ad8fd58c668a0ccd3c71dd62c9d4fc2900 1645620941000000 1646225741000000 1709297741000000 1803905741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-18 \\x3be0e0587e5a2315f379be20bfb71e7c2902f8658b3638f50daf6ead8a79b4613a25f58297633ecf68973d6a4ef59774dadf4def277c349f7b3bb5163c257a03 1 0 \\x000000010000000000800003cbe0e6188f63267863b583ac144f7d5fff150c922c201c9d69f79920a22531b2d7540ca918363cb43d088ede89f5fcda74e080bb59c0d0310c105f78350cd42e5a9cd0c5f6858c759dec6a208f09d41c6329f2b72d99e9a2c3e9ea6754ce5be5be082b395a9a4fdaa3894edbb6f31b8d24b16d599907376c14847ecdb4767973010001 \\x31671c8a0100ecc30fbb99d8ec402b1d60460926b006f61c018e98e081a3ba2a50ade5c4ca154f27a50c84ec739bd6c5cac1674697b8b3a6dc5619bf39325f06 1642598441000000 1643203241000000 1706275241000000 1800883241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-19 \\x3eb87cf47936920f014a294d6c82648cfa444db72360a558a8c5d91fa79aed81cf747f8a5551176526379944ed0badd69f3aafaf4c0f8b2f127bd33502f9b618 1 0 \\x000000010000000000800003a12aa908fa29f53f08612d3b83ab904a678eace544b298100dcd73c1dd498f42513b36a802affd61ec23ebe625b911b7d381ab14f410c9750c4199ae8859963a9d852f8b325e8f44da1a510648d1efd1380250921d752bfc35852a91f4a66e8048cce2492024343260275d6fd17be54dca85a816592b0f92d54e7f641a016339010001 \\x0e9c2fa29c824ec4f703d82ca2c9175eb93480763845dd73b2fdc305b66b9013fed09f247c5f63df57fd0cb27035d032b4a31a0c1975a13391d893d37af3a708 1654688441000000 1655293241000000 1718365241000000 1812973241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-20 \\x3e88a864e4b434cc826dc9bcc4cb7738a3b71011d9e68f4ef2a3c27c45319e159d5ca106f3bd6c69944987e825e3abbe63ad35c38cf096183ad3822a4f47cbf8 1 0 \\x000000010000000000800003a3fbf86a6a91fca287172f9a909c288856fadfddfd1612f28835eac4962e8ca6ab6eb72f59edded3dadc683a3df04645384a79e8fad3189126ff3b0687e22fa2b157e2c9287199437fc884c8593d13d51bcabf44397ea8d7811550b900cb4b0255a3e668b60a9a25f4c7f66ef65ff8060ab7cf2552726898f70cdc7112b610fd010001 \\xd58d6b5fdd6a785f24271aaced7b39a6d987d810cce878d2c6d0ab9136b2774f09f693f0c05c625acce138a4f690034e9d825b004b4401a24307dfefecf0dc00 1640784941000000 1641389741000000 1704461741000000 1799069741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-21 \\x453cfbd45720cff7741a38f395ce6507dd4da39adc6339d63c0e038ed682739d648461a535108f6ed205822cb48a3316d5fd2731744abb42dec72251433879b6 1 0 \\x000000010000000000800003b7e57fb7cef55d6ea450ff084f5471fe104035fb54fb53fa2c22837204b13cd1b7707f6ffcecbfb369f6d77de6a424fbbc87b429cfa7546ed82bd8a22c507756707ae71c7b5956ca66134ec602731ca6b595194946ad13ec0a36ea77e488d401d9d32fb303065d200700017f165aa88842b5a589aa547699f4dd55939459f6df010001 \\x2e883ea27e2a5a110d16bd5a374e2cf45b58652c6620f2f95e62d7fa6e65849a524ccdd4160afe6698f33d517e9666f8aff880593b6b5abcabb51f8ddf4ecd04 1652270441000000 1652875241000000 1715947241000000 1810555241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-22 \\x492c2e712aad7397ee91bf027969a9aefcab74f4f1d3d7e23feef1740c207eda78f3efe86e4dac623168ad78261bf0b482d87b1dd93eb188c35a04c6d6e97268 1 0 \\x000000010000000000800003e0b660ca4c39afba0e78c567b598d922ec7e1a71bdb5bbbec58d0c91c7fd8e4a50daf46073c45f4219ce85dc1b57a992b91d56b86b3c0ec036013fac5471b991fe4b450713d8eef6ea92e545bcfaef027e6f2254aa4954a18d2fe8404380aa06f45082e218232e64cebe81df4919993e5b31c129b27a2dbc93651d2458387df5010001 \\x6c945b12bc15f8f11ddfa256b256d34bec9b9fb6abd473f0f5cecf75a351ef7ab74f1007f8a07dd1ffe020d91afc2fe1d3d809a5e8c8f76794e91cdc91d4c106 1661337941000000 1661942741000000 1725014741000000 1819622741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-23 \\x4ae0cd81466eb66bd9048d2d828fd27be9cfe576467b347894a74ac210edf9484dbd0cb03fc904133f3b71fb77393f3fb889d62c822fa3d80c13e519fc4644cb 1 0 \\x000000010000000000800003eda4b921eabe106d7edf2413dac25c1113602a6fd9695a37072adb90c3313da18b06f186c780793cb6266ebb91a88a212c5c86f461e4831c318549e3b6b297a50f633611825d0e99ed7aeac41f465dcc21c8434535e1e1b555ac06dbece223259a6acadcd9221a77fa1153f3d2fa9e1fc1bb2be1a6a3df32d1146dd51ed7c495010001 \\x0dc4ddd1371a5c97b735b3b772b5e2fea09a46f44d72b70f563c104d2cc239fffea462ea2cfb4ef439eba681546c757b9fac242c870abcce1811417f818f2b0c 1661337941000000 1661942741000000 1725014741000000 1819622741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-24 \\x4d285a82f4b607bb9b4d2dcf0e8e2c65a922755f4638db2075677744726b389903848e97682f655ce18bb5d84809d3aa6ce5a142e2f4047014ae6f9230c6261d 1 0 \\x000000010000000000800003ca6dd5cbc15814c184db7d5c5ad251428cb27c4d672438698be8230c7a71fba1309053a5086547e0588ce9b9b74b5eafd1dc7a30706dd5a57f60fcf61a28d34d79d48414eb00bd4d9de3fc6369245569456031b4e9deb74770ec604b8658cf3b1d9136fa9d4292a3b3accf3428d367ff08076612b6fc7f8f6595d2bff5f51d9f010001 \\x5fc953f71cd1d04517327edefc1acba4d31501fee77548a06e62ebf561258a2fcc3796b7fa94af354272d26ade8ef719dbd09071c363715c00ea365f281d5703 1646225441000000 1646830241000000 1709902241000000 1804510241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-25 \\x55fcbf861a8ecffcc132cf0de5f1985f158538e317bc2f1b611557c6ba2c605ec69ea2af3cb9519b1b5164c2e6fc8a29a92cc0e4270ec27034478d8b526b6527 1 0 \\x000000010000000000800003e19e32d927ad863c6c4b5c3edf4958a13cdc4f2370ab35a1182be7936a195a2f52b18bd599dd8d95a50c68915fab6ef0cff3f1a786e24f96c1f88fc29698b3d51f834b3340f153fb90c009237630087023d493ab4abbee516533326b70857d20bca240b69d10ff9dcc0cae69e5fc433f052637ed21d51066193085d77b274a0d010001 \\x38a5ac330f838506b0481163b0a86c584527122767d41bdde672c0048a553f9e48d3cf20026b328a0bbfbef3e345919649383d60db2123ead36bc9b384ba5c0e 1648643441000000 1649248241000000 1712320241000000 1806928241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-26 \\x59c484a4dd917abc013f52a72ebfbdc0e1dc5b3ab19f7b0236c2ae7f136c679f3bf641ccd3725704aa502c2ddff40c254d62945d6e678971014017cb719bd073 1 0 \\x00000001000000000080000396e9eb80564d7eb6e6f8339df16d98b36647d02d7ca4f202edb1b4a212a46f51d534c40396ee06610601476085594be9dadcaef436d916022d8a37ccbfc08c079b43313e056829d01b162f94e4c73d0f4347511ede188d6f6fe1da847833ad69dae2665ae47b777429de4a86f7e412af30a5bc586aeaa14da5758458a2405599010001 \\x13c6187404265caca59a9d232c1f3d415bbb4d48390b6ed35fbaf9fd1f8358cc01b3ccb82ac780f85a8cdf60cc1776139df1a5fe866f30f5a6a802e4ed3a7704 1650456941000000 1651061741000000 1714133741000000 1808741741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-27 \\x5bf8a23a6eb3692483f6f62c050b0c3c0a527f95842336cfb8a564f88cad293d154d3f1c955fa8409d40d97cdb58ba0402dfc334fe50a6f9c7a2673223098e4d 1 0 \\x000000010000000000800003c24d2e06d34700ee5ee004547b42a26ca98de696b728d74e94a8aa2da0f5b7820b18d3d37503941599b9db99972886a37c6509c12126c4316a4e11c6d3ad4bb74d4ae0a2d5b1901f611d92649311b30fc90fcddbd9c997b2e0dfcd328297d8454496e6bf71f72a10f31d3f1d3769890f6dbaac336b5adba3971632364f5a338f010001 \\x58b2f43132a0836e3288664347e1380f151b90d63739cc92f598939aa86d0fc9d5481e351aef27481337b7c45ee34f4cc7f3c47f71c30527af750ce274257f0d 1657106441000000 1657711241000000 1720783241000000 1815391241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-28 \\x5ce8c60eec96946da0f599b2035ac2e6de4022a64a4c646dead1298e3d5e7e88b751ce6e8c5dd958cef37284a414e6666b163d3f995969fb14a3bf0ab81dbd95 1 0 \\x000000010000000000800003b134e593a0d1815e3a290a03887ca7202e3eb15fe401725784a24feabe09d03daf8e3a26af561737acd6554c5cd13a16c9bdf0dc9ff5ae8c46204c57a1e1cd74a038eb9b43c5b54d271375f5ad719478be3b96349ad57868e3ae913e339fcd854df19ee051297e56b2ebf49469ddc0aa34fafce5ed64d37fbf021521e5e4f101010001 \\xbd937ca74bdb60ef91a0c7d0fcf9296d2a698548e9f41c5c28f7a096064167f8348ccd94dc91d767d41676507fd6b63afcf46ff4a6e9616bdf5a833f4e189808 1665569441000000 1666174241000000 1729246241000000 1823854241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-29 \\x6938027d964285ffbb11d7e508d548b4f7639bc0cc09f11d54dcd11927fb8076ac64b12e831603163f2e807aeca1fca639b075aacf4b13e63c0d2fd1e75c2c48 1 0 \\x000000010000000000800003c93c0f6b391c602b63f6f27f9e3776c701ed6ca2779824ae47bd27cbe2722b4e715ed8d021d3389a429a40f3786ffc39b99ab1331632d8b7c34848bf0a3dc39d99760f91656b465e6aafb7734ab9275f1d3f5cb5b906e384bf78f0bfbb76c3078f445c1416692429c906f67a33c12073992c76235de49ecc93754dc9767df151010001 \\xdd87df2a164ba6dc8babdd59bcb181b793b105cae95f052eaff3d46c196879d278a017cd3737fdf0a6ee1bda7dfd144ca11d3eb9b13980d8b5b557392da94b03 1666778441000000 1667383241000000 1730455241000000 1825063241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-30 \\x6c7c3c2be597fbc9d1a89502bd898b59f6cb57f38b20056cb348fe9e74bf6209f63404b08d46904371b2b0ae0c70807c59b71263c1bd2dbcdf14bf4149bf6525 1 0 \\x000000010000000000800003b967d59e5c86a78451782cff026b59e8c5d43c8f424460b6fb9c10f1942a0ea13c101c3b402dd119f3d615b7ee078ff640fe0eb279926f116db1f62978bf63cc4fa43a4129578dea467851e6cb1f4458e356296b05e9864200f786f94cdef47d9f906cb50b14caa26c3a59ee794e1b9127dd7a5bc1f0c8b9d56e7aec46158a47010001 \\x5640b16cdd600f69ea53de9cd25cd550a75c01afaa9fde0b05398a8d00140ac5baa6d4164168c3b6b9cd5274783fd9053a07e901ee6847e5b15c8f8f699b1c0d 1660128941000000 1660733741000000 1723805741000000 1818413741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-31 \\x6c08ab47d6da166031e8543c8da4e2d1d0eadf4f185723097037b948b2f020dd5cbb6a51a0b34d72b6b6652e922ca51f8227942cbf6cf50310ed045df0750f97 1 0 \\x000000010000000000800003ca2c43c5206e166ec390da34ff7dde0010e128ea714cf5d1ec358dbb13c7703847c9b7470bd07b41f4a117486917c78ba52ece4d6d6e7c61400d4950e22e22b815c968cef6e27729fc11b26d3e5c062ec119b4e4c3ef182eb33e15f2ffbde5e81a6953275b5ab01661cf23a14ba5f460c0e7f453a8be767124637455f429476f010001 \\x4e23c53039c4632a93da168d6164d7fb75547b7d60378a16d6b3f6165bb799df20c2a1203153c26a5191d5c1bc3e5d1c20e8a5859c63a13c5acd5527f26c250d 1652874941000000 1653479741000000 1716551741000000 1811159741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-32 \\x6e448fdf0cc77359cf07cb7beb7519cd351cc611cdbfb508e638945d1874747f6885a228c590b9734c6076b9d8341df23aedd1af30c7e8f5db34d476939eae2d 1 0 \\x000000010000000000800003e753f17b95b29e75e22ac7226273cf17b0c1a08510724d95e166dde43ce998b7d3132bafdf6f15c80c4655dc6a7ee8c1bbd18a39eca9c52110db2ee82a2af323fbd0a7b14397e8dfa94151c4a760e5b48f71730c299bb3245a0fc26e9edf309f36873e5e82c770d772b393232c769fc0f465f1bed9d348893c9c4e9b0e8b7293010001 \\x8310e74a27b589f463b655ae3dca708abce551507f206b21ba695486598f73e17ed87fbece01f4a3fa4e7ca040051a93d5a863362a8a17b0c4f72ce2434bd70f 1639575941000000 1640180741000000 1703252741000000 1797860741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-33 \\x74ec4db899f9a3ad49fb07efff1856f8ac2fc0735cdd4f95430646afa30a6603bc25e138ec2d9b7ba6e0f79daac95f861f9a564aefd1eeeadc28202db1c20b7d 1 0 \\x000000010000000000800003cb8983afe1a838badcd0fe84d886f57c20e32d1e40a0a6a684a960b64542b310ff38546c23c31a89c3ea790fdba8d063cbfb4a86d97651281695f9eee9560581af59b14cfa21eaec6c74c9b0f6149100bb1cae638afbecb824a45589f046a3ba056677c103fde11cecd0ab4b51fbdd3db1f485470b026451e4e8bec0c8705b1f010001 \\x863dd1cd86c3e7328b13b193199e7dea729b1e9fc359d8db29313f374dd35b01afdc631bd5527b1b856af0265bb760bab3526f711ac2cbf3081327ecc412a901 1640180441000000 1640785241000000 1703857241000000 1798465241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-34 \\x743cec993f470178225a4f1a34fca846c3de1ef848b77b8c18eba54686fcb1821a24548017c121dd3d8d8db3fb876584cd23b719148ddf395e27acb4dd091a78 1 0 \\x000000010000000000800003c80c06f75c29e20e0541039772f49a9646ba0b9721348350491a573494a0cb90ed386200f00ba589db8afe3ac97c6ff75c875091918a7f3d0fc3eb18957f58d3bf1962f0923ec045050847937598136e5a291afecf8b28c912eee98f917a736f96cd318a3883ce3de5fca42636995880538bd1173f375b3666084ec081846319010001 \\xc4da47eed5c7e42513c600f243d560da41cd3053fea8d6975404a85885d11000a3ed5592f6de9186404ab93b91f346507c1e35ee9c61fb8ce75c0c403f1ad30d 1654688441000000 1655293241000000 1718365241000000 1812973241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-35 \\x762cf36460d5a5c8e3318051f46e8c51c584d77860bdb1becb40a8b457b76e6878b29fd64056f130301ded15c6d27cad4bcc4e494d7c9fada701d4e15773552f 1 0 \\x000000010000000000800003b3c07c9dbb246d629d5175dbcbd57dd1f1c5369fe173b1c9a1b3a9a0df785176b8d4b46f519206fc9533ef509590d5c0593c778206978adef671a64dc8706b31e4145fc8af474b603aac78546b261fe7f97175cf4e7a874011fc8494fe166fbd8a2d685c00dbd62b00f395f10f6638858062323781f06a45abc8bb7205c7cea5010001 \\x4ae6b77e56a2f1dcd222a8d971751e01e39cc50eb344ed21dea51f5b9d343d9055d816df807def884234a6cdc97f1d4e504419c569d3fa82a8f6273959150002 1668591941000000 1669196741000000 1732268741000000 1826876741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-36 \\x77e0d0939c7a488af03ef8127203b312eee755e90383e4bd45356153f1d8200f24a37e3b0315fe9f2f1dde2a89d668050fa791e8cd7b11ca4b850e9f60c87059 1 0 \\x000000010000000000800003c061573f2e6e5d5197a18d3aed75e82e47dd6c7df39fd6cc4d823e5ec70480069c316b1c40841840136cca95f5f196cb3bc79ee9bda5bb505238a78629811afc4df022f6935ee941ce00ade826727bdeebda831de90ba6df9d511ce82a7f418604001b0c1cbea7f47f06576b4d15cfcafa7ce5ccd61347ffc53f620d60e37fd7010001 \\xc47aa90dce4677f67c5d8c8fcece183e325efd947e4ad85562e961a998686911605de5610bc0073f938563c50149b11f5526a573f48feb25fe3d64270d1c5101 1641993941000000 1642598741000000 1705670741000000 1800278741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-37 \\x7adccff69113f344e2eb53b32bdece9b53818ee7d123e2a7f37c15b2fdbebbf5a8ec1d4b146480589763b33f850d786b6c5a17a8ba47d06f9259081cf8aa61f6 1 0 \\x000000010000000000800003fc4f41a679f34621374d045d982b7534169005aedf5068b1d034c0eb343c1af1f83b606afe10d863e8daa7ded10b146bff3f7dd0f24096b2fea7725f504b894890df715b069d334f7ab249f968334914fa56ed3718e6866431d4490fe23b30c23f8e8d1494133f20959fa73c2a00c59f1046816d0e143ece0bd4748e83b97d7b010001 \\xae7dc72b050620e76357c927c829d41db70a653dbc49180032b5bdd8a07b09d8698c43f968927f9076db5211d723abbc783a9ec670e29c0ce120f57a35b30702 1669196441000000 1669801241000000 1732873241000000 1827481241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-38 \\x7d206f16f106085347a449c63e6bc71842090a6384b3eae506cedf91db7bb6971ce881157b7fdfc0460d66ed832b1ae9ddf2aa4bd4fb057a60eddf5b4af1838b 1 0 \\x000000010000000000800003d61c1fe72d11e1889044d71c87f0abdeb937ed16e02ca19ddf1e8a0de20a9f3beb9f989a985f511942bae6a921469d4d79e2030f8b3b509e306a032c08e940ac3036a4dbfef1298895f16329306528f5f810712e83574b1a3d54fe14fc42cc75fa245316d2ca1b07ffa3902ba5b4ab78fd2b2a459363671fc219fc39084e5613010001 \\xdce82a42ed0f6a5981c1ba2a9ec3894abeb8c92fa216050f4750724edb412db4616815ab6cdec554196e2e3c18d8905d3ffa30e5d456b7f5a85473ae737bc805 1661942441000000 1662547241000000 1725619241000000 1820227241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-39 \\x7dec919415e7f6730fd2b2ebe61f38dd1d026176486feea65320f0d9efb8d2457e86668456875fb18a1d643fb86b40ee840dbcdd006be40d824ca44fa2a00236 1 0 \\x000000010000000000800003c6a5398d67b49a30c32f4473dacb94af3255171c7d15af2516ff367f0c15a2bb996f126d8f8b2f7ac96929821ecf3f98de71eb79c2245364238ef57b895fcd992655ac5b4aeab502646bde7caa9c36e1c908c59096cfd2b647b74cbbe9d0cf0fb34e5c00264a4ca0c6688e8baeeec64a11a5396c5e77df3d9ad64782d08695d5010001 \\x50b0a67135d6c4f73ceb501b5f4225ac7886f57727556f92de7c5dc18cb0083785d03c255b6bc443357950829031148fbc8cdb6980a886848776f17f8ebfe800 1649852441000000 1650457241000000 1713529241000000 1808137241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-40 \\x7e84c4be615df09e1219535ed04a1f4b0a4a43a6d6948ed884b867553b010e7a984f27019ac617fd4d0973fcfa20aee28f83453ab12fbf4458303d78833f11cb 1 0 \\x000000010000000000800003e3d855987aa741ec0f137764328eddcd5f881aace4113f276eb3539c37b387b02efc18dee54b8928a14b30e9b2e38b08cb6faf55b52e23490d39e44a5db626f60934e637f15206be7d0a525719351e98b360759a3837f398414c3fcac0a62cfb2be7645fe7cb62a69a735e8903678dd82eaa311e0f340ad30a30c3e4e5d63a0d010001 \\xa6e4ffb2866f5a3ad71f456f03d00db1e0272602e70daeb56a3c531c5cd387867796b1c17811c993adb6dff8d49118ea46e5a275b9c681d9d80a14255ab78b08 1647434441000000 1648039241000000 1711111241000000 1805719241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-41 \\x82a8810e909a1a2d3d402ed8bf4bdbdf8d4b506ec293d078b3d8080bb27ba641cb1299615d93450e94b1c83e6b44429b88042d73959d641f2238161421a6b08c 1 0 \\x000000010000000000800003c8bfab8dcfe955857be5c70df88a17b33b4b9c99b89ddee91d3ccae78a345ccc6487db6d8403d68a149eba698e4c916b486aad5b703a41cdb4dac2f4db3ecfcd51a7412f40a46d1a10c24038cee373e19008d0c34961dc9e059c60c3ade22f2d036fa8b9121fae64d16c6f43f693f501b26221c999b687c4352837923e6cabeb010001 \\x61cfd17fb69474457af84cfbb2e3f5a0b947bbe21ce3f7bc7243fe1357fb89dda67bc29d438191d424d542494332696e1e242b58ee9e45d9d92b6339363a1a03 1650456941000000 1651061741000000 1714133741000000 1808741741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-42 \\x84c8175599e70870f7bc0c8660f88b6ec6e5018889a57602b2e54f28af5499ebaf7848fecc94952d93b852a03f04c5324144c5b3f0f0588f6a5a44582ab31cbf 1 0 \\x000000010000000000800003d18b1a2de9c6071ba6a9ecbd97096209f1d108d437935d59949f49109f344a80cdc1028a4ab2e8e18cf1e620bdf0a796be146147e42530db00ee6b5c490c44938861406cbcf6f7a1097d850cca72c0b2c6be2666d5b1be2b80d3c0dd795353b0f0381be6bc3b9752aa4f3dad19e2b651bbaeb99eca690a4acdb9597ba8820af9010001 \\xc936e0e8de0df0b939eb7e05e4dbd6cb9747f21b38783ebb20df3ea1ff765b72552a5324f9e096662f1c58e302355858fdc85df2c0e08c5b68e1139233c9d800 1647434441000000 1648039241000000 1711111241000000 1805719241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-43 \\x8648bcd680f742a967333c682e83630ca25f69bad1ea513c5e08130006eb74b1b7ecc551b6d033757f09aa56c390821a7bebdfa273702cef28ecd5d2acc57784 1 0 \\x000000010000000000800003b181b90ecdcc4c4bb8613a3784616e916b38665dbe38de69220a5ac1850e7c031bc518b2ffa89adaab797b57c53bb6f840dcbe89d7bf6f1fd399e3666e007f012927202006d9bdccafdacf72d5d39f6b2fd5128249da3503b79d3ef568a643ab005ad3a44b11f65d19ad091c78ab6d8917abed104f9f0d2a22f5f85f3516f05b010001 \\x31f39216476e7f6fde6cc7bef58324366dcd36915b2fac8143ae33f2195b8a5e3c399f011afcf1d556494ae69b61f2c0a8f26174d52c714b8bb97fbcb1363506 1657106441000000 1657711241000000 1720783241000000 1815391241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-44 \\x89f4827e986f50832b7ad1470859d12c5a08944d4988a7a08d1a34aa6532e3955ddf84a50cda8be4ed0e09fe9278ff9377c058710c0f5869c3bc039ff70b876c 1 0 \\x000000010000000000800003da2473e71e6dd094174b3f33d956082f02eca9943effe92c706e3812831e73ea431d94b8c9df571d3926fb7692e2237d37b8da0b08f1f589cb991c3449c1dee40fe9de9b84a22a67805fd91e9b8f4d0e5af267709bef5aced2fc0fba0f1591a4f68ccf728f865bfbd9d27fd4cd4e6c686becfee568dc588fa1a7ed1ac8290f49010001 \\x8bdab262bd0e22f27d3285e016b77f7dc2da270a60fd2b333bc7af7401b9d225501bec82112a7a84d373b6fec9746dbe455f047d8fb9c351f12dbe57b6b21b09 1668591941000000 1669196741000000 1732268741000000 1826876741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-45 \\x8a98a94ee1a6d0681c20b365fc5c26da75c22747c60fc8acbb7a64729503a1596f81ac6715ac5f49e5849fed68da3edd983a05d48b74e47fcf5d351bd9255cc4 1 0 \\x000000010000000000800003b8cc76c9774633763b6df10e364531b1863eccac02a9e59472fe8b13e493b93723f0d6244684daaf9628191abf9236e7fcf2f65cc79d6275fced1e9fde606d516e01abca4145838f55cb4be0b655929e91db1742991a1236509a8b64805b56a3e0d165e045194a46b3c536de0c2a96c18548adb3237766d7868470ea81d1fb4f010001 \\xbf296b5fd9dda3f431ed33de55d38056bee2517983e93bef05ad783c63f1dfb0a932b40964ae5c3242511d14608b693f1f3bf1999f30ea9549b6b68de5b8100b 1669800941000000 1670405741000000 1733477741000000 1828085741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-46 \\x92047d45c8ce22628feb77925d6133a55f7e8488c89634692b8ea0068cfc2a368c3fc2ca0e9ea364e227efda13aff7f910ee98fe7ff6dcdb7be49108708a17b8 1 0 \\x000000010000000000800003e30b3987cd8ac3efafdc36349d2bdd5208c685fa904384a9b76449550e87a158cf78ff6d2977945d39cac0bb233fed351571724f75a65cdec9a014d9dbdcfb6b972011fc0b3cd036bf05385da9537e54efe3910275dbb60a1720e5314f1aa323e6fc63a3321c186adcace9b3cccdceb078cdd49259c0b8dff4b8ba0efa9c63e1010001 \\xb767abb1560b481a35a1fbc8aa529e7f6351aa9a9abcb1c3ff74d19bccd38200de47669f72a8465cab7039e696896e8eece2b8951a63940cb95f830fb30c9f09 1663755941000000 1664360741000000 1727432741000000 1822040741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-47 \\x925cb1685dcaf286f33fb8196f46470d89e4544a5eff4c0578c78bab9608a45cc322c894932023c234343cf520fc19f25dbfde539d9c789931bc3128a091f020 1 0 \\x0000000100000000008000039a44fbd9161dc8dd422f0b44d4cb9dcc41475c8de46428106b69405b92b2fc044d84b3fef4991d6a182148ed3211839ab6cd61a9e63971b5915dfbcddadaf1868f2e5db12420a13dc8634aabffe0451674c0600a0187abb7c32180993214ebfccd48cdcb48f8b5b4c34bf966a196e140b30be13168706bb88afc95dc6802a069010001 \\xc4a3e0a4c678feec3f6adbb962582016da07f0b52354a609adc7b207c528290ba5f57f0d5b3a3a5c5ef84f339db67ef47c754944e9a8877312e6d18aba1b0102 1661337941000000 1661942741000000 1725014741000000 1819622741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-48 \\x96302c24a60c2de740c6c1d6beb7919636024e7c32322fae9eee711de281bae0c61dcc0b0a66c1ea68c416084be0027395291a87e60a017f1640a4e0ee065f25 1 0 \\x000000010000000000800003c73a33bf7b9857f588386691d1fe34c1678986f803f41060bcae1be6857eb163dad4170d8476566859f8c2ff08bb57486d6cc116450458c6e9f50af91e9afe5081e6421d5faa8b5ada5b4ae533d188aa05cb29e719d006e48bea79b6a616cd95cfdc7c5ec859e6b087864cf1e4aadbed4eefffd054718e4aee775f4311879fa7010001 \\xb933fc22cd9d453c0de25e447ff63838f465a5ddc8791cf688a6dbdcc32bfd397a0c9bba3da9e14b5cc0e9920591349940b7a21e5bf46e8d0e43ed0527d08b00 1666778441000000 1667383241000000 1730455241000000 1825063241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-49 \\x969ca7d14d0a765155605dfb43dfdd95aa9cdae7996f02ca29a0f62017bcddca28509394763d46df5b97b887d0a6aeeb10f27eb5590d2d14a8ea38e324638aa5 1 0 \\x000000010000000000800003abd1aec87afb80631494b521d5d7c5e618a66442fa1f871cc16e6cc4f4c4cad06817d7b43bd86524078d832f5de38fe4f426b498b569676923de8d0cb23b68d9653861a2715de240a720a50fb2920f0e30b2b03023a9a1dfbec0fc8f2959f16f7fef91db11ba12b97b67e11d7ba25d28a5af3d2852c8605cd7c8c88ab0f933c1010001 \\x8f73a227b383c89240e4b7f3b029200c0387296df021c3bf946944ded2b2079b28c54ff3a5b6e479e0d3a8ec9944c865909c427e3878fa44e175d513fd7c3609 1639575941000000 1640180741000000 1703252741000000 1797860741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-50 \\x971809a45288a05c224e3cfbdc16823ecc8ffd2aba0e64be4073da79398e59d625ba59e11a68e41534d296cf748af41ea7091c4c0cac58832dd0b59a37552281 1 0 \\x000000010000000000800003a5416f74e43fd08c76a69face3e6db7bf01dd2d2523fca7090759494c895bbf41d27f5744b29a3c44a9aa5fab87b4e640e0f2847812a7460ade05d8a5c031de25caa9e1918a869743177e66f90b83f98524d8534c5a3b9b9794d7f43d065e5fc34ade22cb9367ecfebaac060e64af3dfafc09ecdc2dfeba5cb767bdcbc734c3b010001 \\xf8d33a7e171f77bded0c65ca15f0669e4d6e9dd247a5e01148ce0f57d0854792ab887ee4007e335cb4bd165a3d4bf4a32dff15e2817c4b2a7a2e345d6001fb06 1646225441000000 1646830241000000 1709902241000000 1804510241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-51 \\x9778a0be73687e60842f76fa663282d11c9d021b5b46690f4bc49c38b1c991ccbaeffd99965b2197dc22ff3c20318c541ae0659896cb6cecb8078158889912ad 1 0 \\x000000010000000000800003a722981dc88fca9abe3d37b8c5bf7f0e97a434148824be9383ba95e0d74de402ff066fd7eddbe86e57c727e32ecda7ae59e461893ea4ce3b3c589c52329e148ecfb9b537712ccbe40e5a924d08846803dc69b217e889ac409de1d4b9dbece42faae66cbc3ae543ff79f636de150e00438b086ff497e4dddccb2f0127e654f071010001 \\xb17fad8adeceb98123e56eaf384a5ceecd1ea73e7315f98b0dbc80972b1186322aad64a2a076f624762233c26860376efafd4034cf1dc4d3ce7786027b7a1c06 1661942441000000 1662547241000000 1725619241000000 1820227241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-52 \\x9f941ce81d4681f1b7d4fcc43074ff84dcd6410138de5be5eb1ccf5e04cf91de01df88ae561048bb36293b08adcfb7bd6fb2eb09b80fa9cf45fc51b32791947a 1 0 \\x000000010000000000800003cfa23da329342448bef8efc9424d71a5c7af9dae9ab1152f9709580195866748bbdf0fcc1a516014556bca0e7e997bb65898526b5fc7cfeabdb0bdc97f7ae412407fd5b48bf7bd272ab8d339cc57bcb807b2a92d2885e841b0bb7f2ec4ebb095e3096253ab8c8cf08fbf5b150bac5a6a6d0f3a4ac8ebb515e6774f4c979e6d75010001 \\xa1cc08e7311a14539c718052b3060ca56c5426de6ac7d08dd01d0130062eb395ca28b3076457eb1fdd2521d3121d768eb1d9b66b93cf5b70ad0326d2ae562b08 1648643441000000 1649248241000000 1712320241000000 1806928241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-53 \\xa234f1ae7efd9d0c155bcb8fbe8ada74927e1093857d010276e91ce05c44d58e6a5a85583cd4a506caec3ae57f98631446ff11dcd863a844fd2c48b7e95ac79f 1 0 \\x000000010000000000800003ec898c88c72df1d1f7b307fe0a00d6d351581fecd68b71253ece4c1180a09385feef4ba31691b352a7034df0d881e9955f842e062edc60777f572eb4173ba8ae49a514b9281f16d80d84bbfc2415139233ec14864aaf601e16ebdb82b3706e43d4fb602bf531fe2f2d7c9b9f5aa508f3cbc388242ceb9cdf6f491c72b019e32b010001 \\x612e730c693834487324fd9f8ab5fd4b2cd186d034cec23dedeb7c5938ed675ba2874a41fe698a8fbe6f897c798e8004bb8e580638ac778ee8762a4d3e31ff05 1653479441000000 1654084241000000 1717156241000000 1811764241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-54 \\xa7e8ab3770cf49f3e3d4611eab4803d8cfeeecc3ea38f649605cff7e1b59fd896282240cc2d7d6173d09c9e6b09934c5cd0f5d29f19691781f9eace489498018 1 0 \\x000000010000000000800003dbc180103bcbab830057f9c63e2740c7079e92de21856781524b5b2b5bd118e2e4449058b47d724228d68d4cda473761668703f9ec711fa94c2e73fcfe594ce6360a080ed27f059206c1d657e4385315bb946f08fdc354f536f58ca6fb0807d0e3e9568c04b28d3069fc25d70ddbc8cf063630e1708fa6dd1976819c30236e0d010001 \\x03794386aad01b7ea15f5b3c9ac4c7a2bcdab697e1d6a364994e3346e4cfbfd5ebb71763d4c6a723b3cb440cb476b25ba9b9abc99164177793596c806b021a05 1652270441000000 1652875241000000 1715947241000000 1810555241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-55 \\xa9941504b144a6180a4458fa9b8f2f64f9f1ec2932f1a3e642fbf7b32cab7c3a79e3035b73d59ca935534bc7ebf4af5a90969aa0716d626245598f4a28341bae 1 0 \\x000000010000000000800003f566b40f6dae7e5e35c28d352bd4d8f54b4f30511c9c4a965321c27c7ef869c76626786de4fd19838cd065878766410e4db0c9b73d842d8f0dec2a66994cba6465c69b38cffb3438afddfadd1d0421e0072fffdbe1b69510a28f3963082aae5ee3996e924688816574f5134a595e96f509101c4e9c2a2e012bb873fb3a970d73010001 \\x6d043b029a8c3eb7e1c01e5a0784ac9f9bf794fa59ba27456bc657fb4e08e5503abf6c81a11d4c1f104f240c7b5fbc31fe84f2a52f57d5e79c435f7f41a96e0a 1658315441000000 1658920241000000 1721992241000000 1816600241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-56 \\xac0ca3a8118d4dd37625ab327ebb705faa6bc437116599d59705d4229c53074a3a9cfcfc0fd7bd056bb5fe98b917ca4b503cf9346911f29a5e87c63b597e6e2d 1 0 \\x000000010000000000800003c0312c16c61976db3cf7b3ef5d08b0c2d6a16c7fa3a0a445fa3447d46d2f4db0eff99a4e6e5b0fc081a3da3a593585b26c3280d59b89c2df08e9738a876eda5758d32785d10a61ecedadb39d52d414bc0e2f3adcaec57b1d6a495af799f7ff049739ae943a1af4cc8b1ea3d02944f345a0e6654cba4ed62e688e06677960ef61010001 \\xf08a6955c37a4feed1f865920b9dcc9019201efa7d23bcdd58fd0e109ff624ea38e0dc591a97a69723eef5a1494c30156d30cc4282c34ba42c661d41ded20905 1646225441000000 1646830241000000 1709902241000000 1804510241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-57 \\xae1052ac88a4f29460df2f7eed5430a02fac27e4dcff51a9e644bb6578ed6ded040b8067c06b244c884bb4cf3d9f8cccb510c88818b69b01863a03c89fefb2ad 1 0 \\x000000010000000000800003c18eef244744d33f6a03598cb4ab8befde8171c9c99f5614090e0045cd2bba79bddec7da32b5fb2b78aeb6b209359a43590916489aaaf62f0c2ced8258d15229669cbc033946a7bfedaa57fb8e65688dac5156513b94a9a61c72bbcc6e275f721eac035a2ca2633f9031149b164ea1157a7326c95f94ca54b6e0dcaa5d6816c9010001 \\xe565ba412a0190ff6fa584a7e30ace5438c4ff7d4f42f005a670e2d8166489f99de30627dbb47d6b8a08d9fb048f9c6dda9d57d99172493572b12b0a4edcc80d 1654688441000000 1655293241000000 1718365241000000 1812973241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-58 \\xae788b576d29800b4e5e649cb1268eedd93fb42b42b68ad6098734ff5ff334eb735959fed87a45e9cf586549e21e4b2e776fd76c9a64e47a9b29d81d51da3d9f 1 0 \\x000000010000000000800003f11796ec4610703084141cec9e7cf9466e93aff2b91d534a04136ff296d1b7bfbce1b00d7114326d0e65385d8870e32d41b60a9633904d55ec23178b9917d4753156b0870f7f6133a67a9223cf10d4059a766aa66f2b81968acfbcb1e4859fb44b213fb723095b781b95688b92d3da8aba2efc8b7c71b4d1352ed8c8a2cd0169010001 \\xf7fb2e6f562cb2bc89695238d3b4709ba2f028b4e68b722983d4302df8c1b10a562a7352d977c81e97e4bb7b7c9b6c05c257351d3736cde02ca1e9150e415c05 1649247941000000 1649852741000000 1712924741000000 1807532741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-59 \\xb15ccbd9ec772093d8717c1190ef314c1f2551bba9939bf4c78f5da6881ffcb1783e80341b2c9690fea18510a1a98909773ff965eb212ac2f34513d411292c24 1 0 \\x000000010000000000800003b7a13e9f7b16e826a02e6c615fd933ce182738412084f39efc56018fb1a684eaa7f908079b467d2185709f8f0945a884b54160c1c1931c9313cf95fa6d898db1c5d11d0f3203b031ef016741ef1a40ff025dcdea45875dadd20782cc6cad7cf05122f50d93718cc6be64d9a56935efa80916e89c028c05c0c4c1130fc4369c8d010001 \\xa4c94350d3baad307f3a8fa53bc193e00463274abe31fb3f9c3aa94fe791cf42713f64dea039ade82c962cacb471425fa33abac1863ead3c10c0ff5120da4006 1667382941000000 1667987741000000 1731059741000000 1825667741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-60 \\xb29cc975856835a2e24a9a2404f65bba118eb17c1be07adb6df5357c22c5980725dadc4da0c4428b1cccbc8f6a21dfec59654e4445eb897ae35e159d45315f22 1 0 \\x000000010000000000800003d9a99991a7830c63e24324ed6bae521fe941be767bbe1fce01233cc93d8bd550eec5e3dffc2b7f99945c8dd2ee33b6d85e30a1e7da640d7fd699946bb48109897b6ab4112aee5aaf265ed156a808974deabc4c100296c70445f010d0d6a0159fb79cdca5a631836d708b12ae409734216b2054d28395a7c01b7a9f14de34d8b1010001 \\x275d440d9b5c947670a4e4c8965cd2f78d3b67a0aa5f7eb5ec5311b16ba364ee12a22f5d0445e5abc2f30ef55bcb9d9f712a3474f5312274096ccfa85cc2b205 1669800941000000 1670405741000000 1733477741000000 1828085741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-61 \\xb2a43a8110ae9584e124e655370867793be1758bd776b5b9b38f23b857c24c5c0ccac641c592490aab1cd42c01eed30faa0753963a5cec5bbafd655ec9ba261e 1 0 \\x000000010000000000800003cdb07061f4dc5aff066cb644d6452c52f9bfa7990b2bb973bbf1e86603b4addb6e6e91cf522a4cbb32b13ed3e460570642ba8ab65e875c69da80d064c94271dbf6cf0ab850e0c35f1358aff848dc547b61473b08f35c25c69cba797a9638f19416d01b18706a31c682317781f50fde08411d43326613e15654612839f3208347010001 \\xe2bffc314f37949938ad7004cd27b6b10574301c89b9c744e445e3e70d32b01781f0e6808084daa4c60f9ff1d19c2be5bda9145dba79473d3b6476c878b36e0e 1647434441000000 1648039241000000 1711111241000000 1805719241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-62 \\xb3942fe3daafdd7eabc4b1f9f0a4d1d3ca83a69e27cb809a54dcbcb24c3f01fb92eaa70da6353bb3c868aa3f2027b587ec268ed9c0a7c7e81a24e3de9908f4f7 1 0 \\x000000010000000000800003aa8d4755f56093a1a49a21d3a9d46542fa7de3ba273a731fe13d9f946e905f5ee5c367c04cb21d92013b144c860bccf5812552231916a625dd18d5990d6e7a9efbc3b19863f6a39efc5fe0e4fcc0380d63d5c2216054e36b29d430918b8a2eb4bb06931cba1ff0983520c14567740a226b411cc5112e71156b529d83d60118dd010001 \\x63793b5153cf6f5848cf206457e10ab9840b67e7aa42018cb7018edd4dcb025ffb9da1058fbe3fb4431cbd00f1c00f71ec5af2968c850e49e616ccb8f34a680f 1638971441000000 1639576241000000 1702648241000000 1797256241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-63 \\xb8c82d783e3f21a3ea60b5226872b69102654171429885a519bb65a4950883f9fff81db616e5606caacbcfce39ac38c657960adac9fa01d72b7ae0b1f9e09ca3 1 0 \\x000000010000000000800003c3997c356bed6f8781ae6620f8cafce11d5e23e92e76e532ad4f5e46ef17a78e132452403569ac5d71571d2469d512f86bc594f3b9840670920b9836c6782f2d577a2ce6778eebf61433ed99b9fcec72c9d10a99daa4d8fdd85c9bec989930a92d47d7ad4720d89fc41d1ea9112fbd494a6001eb9a8de9d62217dfb274da2811010001 \\xb8b69bc2109190719e87f59b37a7326b314ecc44a1cb2122bd6ce7b6edd0cf9c7c0135c226b2d5a0d8327171d1e779888be7194a09405b608887dcf14f680909 1654083941000000 1654688741000000 1717760741000000 1812368741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-64 \\xb83cd9d7b8cfd7b78de8fdf82b2b1c82e1b7e46999f59b07e497123867988069243af04f36e9cb031ce6f706924ae8a486db759303053249b7d5503e03089007 1 0 \\x000000010000000000800003daf83fcb37974eead58568ede1f992b78ec857ba05dc8eb75019669600d68677ef76b771ad567e9fd224695487ac7bf045288062a897033eda69697255741dad35484e68a667b69ab409b74d347ff35f298f6166d91fc2eff8aaf11a0dc2e770ef2988bfec19014f3f740f32c87dfede3b25f699cfa7549f0b64f59302ec07e9010001 \\xfe856f40cf563dfee122827fff3d71adebed6c8c82bf46cc72731ece0b673c98d3a709c40543e98880aa3593d26d45ce25c90e23cfab4b8ccfd22e3c1d348500 1644411941000000 1645016741000000 1708088741000000 1802696741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-65 \\xb9c0b4cbf345aa83e9eb295a200080beec96d047b238d5c478a65fe7eca92a4ef69c3ebbec2f05bbc6c5b2456a23b0307e6561b65ef05ce1f22c262821ebb34b 1 0 \\x000000010000000000800003d0e66d80fea717f4e1da1bbb862a378eb7fd1b934e9f194035f82ea21b09ea9977691f9b0f6bd8769abbf401144019bef6af2da3ea873b70a182a63c6230a549d7226f6663f6fdec919cea9c3ee9457e8bb57939eadcb6571c9ee028f458902402697a4194b8c1f5c5715212c493466e02f776fa15cba1391792d50c470eda63010001 \\x11ad56f01f019349edab7462a111d9ebbd7b7b6d1ba0e6f471009506226966178d1c15b3a353514ae199a1b7d5b5e3f6d9bb1efc241d34163f6088e3c3400f0f 1662546941000000 1663151741000000 1726223741000000 1820831741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-66 \\xb94418207c5af9a742dbc451a07e66cc9d0202a01bb78e61974bd7812385a6022794d87f41f5ecb946853dd16801431b9a711e679b88a97b968fc1f66a854b5a 1 0 \\x000000010000000000800003c14be6d00da2bfaba48fd12bbeaffdf321009d9ad309720b3dcde4218769dc7a4e375e9f218fac9cd7f8e864f14fc240c6d7bea1c9f58295bf26bca95c8955227fd3e4789bc9c010c10a289f61f8bc89c2e3ec66550ee006ac18248970165a4c51ccd641b427d2cd8a8a6367b1f14b0fc8b7a0622006c8724881596db552f241010001 \\x64dcfadd5df49500bada833ea42c5eee4158ed7cc6712ef64f0416cd0afb562bac250078eb621933dbf02d20abb199450fd7e6b6e20f5812749358d15336540c 1663755941000000 1664360741000000 1727432741000000 1822040741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-67 \\xba1c6a80427d53a0bd80f0db9fa40349f7f1efd7ad1cb50eb7af8a57525f542647dacb822007f89304d166c5f4d733df032432c78a5963a7a6e00363a97ee746 1 0 \\x000000010000000000800003a80cff348915f8cd2a57b44e640636143d62ea571c41077efdb6bd2c296838424f11d3255afedb60321e79a0f71ceb8854039f9fcf3e952f8ada796664bf08439d49ab263348d3053c456acbe88884718331e14eeb59db82101401cab4024b59fd80c5a260f4f85816068c86de74353e20e646bdb5ef5a7db271bd8c7b39b483010001 \\x5cf0c3c498a52958a6ec438401d1b19a2a0dae5d2a218d9a5a160b1dcff8fdbe7c0ab3c0624ecab6d719616435464cf894368be1f6765aee3291c45451a63201 1640180441000000 1640785241000000 1703857241000000 1798465241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-68 \\xbcc87d6df3dc5a814c9f71094a0469f05f33911e1b67fea3ad24034bcb1770e0886a02305bfc29d853c11c7db46cc885b07067445fec06fa31a30b2451a8b774 1 0 \\x000000010000000000800003cf69afd89f1401e54eea9b7663c30e0f685f74f39cff1d4648833eb5b409bafdd94c18fca1aa8e86c4a8818352e09b37070cd3f691ab1b153815c02952578f7e9fe1051faa2cfc4321d472d4c5b5ac0c8f84198d834625417d997c2e2b8dc458e88f20f06187c5b7372ca9232ff2e0d9795483d307faf6bddc7d925726537cb9010001 \\xbd0fe3656ff362801d7c37ffe5d57e41ec8a50ae6584726a41c058ba688b351fadb19b7e3510e5699dc64803df74195acbe35e4df14da9a116f1b95100d5b606 1657710941000000 1658315741000000 1721387741000000 1815995741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-69 \\xc0e4ddf0453337c6ce13ccdc8c64d1a711bf02ff0125dcf301d456849f4cc36a4a651d7d579cfb2c5318dae219c94bbb0d7d186db5664c9c3dba668501dae03d 1 0 \\x000000010000000000800003bd9714b307e25318dc1da6c15057c274722d878bd28d7d7c5326d8648f91d3c1c8a7cd5a33591137f7f215b57c44acefd058d8420d4568bec5b363fd5786741d28ab655f32dbcc4bfaf6617059adb67578f827da76ae3de4fc7314f238ff4ebf053945fe15ca7be050fad89d093a82eb180e9fbca10f87899a9ba1384bd85663010001 \\x0abfee46ec7a650d137472d5f48c18fd95e625d49f29e079b827592097830dcc67652572517fefc708fc99b2582e71c969992512eacced81624fa50dbebc5d0e 1648038941000000 1648643741000000 1711715741000000 1806323741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-70 \\xc380c9065694ae2b47013913fb30c6ff5a31752b59cd9b9a8679f937aeea7008ceb8eabb6a4859aeae80cfc69f0e0d288797ea261471347ceaf73bc6b7ab0e2f 1 0 \\x000000010000000000800003d521c2ecde98f8d27c213a3d64e720f0e9c54f9232e32c0cfc96ce5843839c3e68f8889a053d7570ec3fddc98b1d0b3666c39521671b06a7d915f46a7cce96566eb1683ce8b77132aef53a8fcca4c11f9b661ae3f1ee86b6a8350e190d6a139f7ba9887ea48ee5ef4d4ed9c8ca95383c7aa1f3a90a8a5300d78d29a50f2fb895010001 \\xe0e12ac22a673ba040d99858aa9140a27d6d1aed89b9c0e2220d45a4bf385041b507f36bccb93fee836cc8328d1831b2dc12db20aa7d9bc5a84df560bab94d0c 1643807441000000 1644412241000000 1707484241000000 1802092241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-71 \\xc4e4697a3c6ea6c22ac553b2387f4868d049f2222f9e0a88f9b07fce58c436ec134e137091fa1db5b09ae381f82936ed20121dea87335c90892f3c162b8840c2 1 0 \\x000000010000000000800003e6215bc302fd576bc33cbf2f8bdfdca548530e18a854969290c13060e21fa1ba50e7e000e0afab0c5a78eaa6b5173e82e281ced481be98f526e43c4f90fbf0d526404c5faf6548869497997980d1e48c733358c4660a44ee9f8dba3b274ff90f82fa215ac33bd483b681fb409b3e3ba8e07a1287bc4863ad4ea3ca0cf170aefb010001 \\xcc484a6fb4f0d53b4b0c871c8a1b86301eeb224d79664d4f6abd9b1c8353174c5c2726c396bffd8323900daf98504d522820da026e27407113a1b74da868a004 1652270441000000 1652875241000000 1715947241000000 1810555241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-72 \\xc7a84b73b1e5b2fcbc119f91b8401754422ae8f54283bd849858019a278857e4fe38ca0d5ab793266193bb659af74faee50fe23665d041551cc7132c2f65cc75 1 0 \\x000000010000000000800003954fba3d3c0e6afe49da4e499398d3a7ccb6c901b0deac3fdb8a7a29968bffd24c2f5b305d2816e58a3c4fbe765945613805861df94c149bcc619ca779461bfac8c792bee277ea9f23b0f989de7d00a5a1aa2d9d500ed08d98bed0f088628cac1c3f2bc4759b51d3c528deef6d2285403636615b54f4861b55f8185b1b5656fd010001 \\xca68cb275b3505d7396e9bc7f09a8dfd7e9ff0fc9067f03193d1195f7daada024bbe9f5ccd9d054dd6757dd0cb79e052a88d158f3fb804a0fab60cb3e029b702 1640784941000000 1641389741000000 1704461741000000 1799069741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-73 \\xc730d11371e461ea1afba582ab3eb7cd27a0b101e83b2e6b30216b982fa48d7d1e55bf41cbff54b1825136c10925e9e16ad35711fe74de27caf1c691e5fe2ae2 1 0 \\x000000010000000000800003bb6e661900a10a92c9b22578af981de13dfffa58660c650b771e4ac6ce06802b5cc45550c9f6fc2fbc6af3cc0a94bcec7bb796d5176613214d9b5f1864e379c09a115586e55628310a6bb41f8e19d0805bcecd9765b3fcd3c229776fc5a5936a116a0d6e1755e9dedde537297bdbe8080ccb096198028f34ba7e5a8e7935dd41010001 \\x1bdbd158d50e21ce6c6e1f0ea0b22b2d2b6c86c9cb27077e12c0e0e521cc3922294ca71c8fdea719409280b61dceada9808e671ecb67363c798fd021bb8df800 1646225441000000 1646830241000000 1709902241000000 1804510241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-74 \\xca4487b3984da978213073872d55bad6f2879bad4745b85abf519616ddaceff838b7c025d3bdd8b21f05fc2ebfc1bd4653e0b7832dc24ad59ac139723092927b 1 0 \\x000000010000000000800003da89188ef110b1ee2d0b76a521e17f60e91d62094c9d1d7ea0c7678943f1885d057b284023f81e214561420898b8809ed69ad79448c96f1570923e2532810691cb678a6a14096ede7d317ea91d244c6db19e449fcd58e4ba5ce6a0e99bacdf1fa158ecfdc7d4698b2b1847dee0c5ba19620998fe74f55832ed6c9b89ecffe37d010001 \\xe0231390004381d18f433242010b27d96709563f1f1871bfafb70ff89df8446502301385bab4eab1323ae60627500c33e18579bc749196be330209dc32d4710d 1643202941000000 1643807741000000 1706879741000000 1801487741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-75 \\xcd246dceb090b8c22a547c6ec5a7eac9fd72679a311b8d7023cd7f5f87460b09c2e6941359fd1059c704a281f01d99ce0dc6fa958223bda71debdaab588383cf 1 0 \\x000000010000000000800003e72da8b937602451fcc6e80160dcf764fed47937063d873d7d072ef0486de67a19a6f16691e4f988de5fa7deb8e14927aa846e5f518a4b81ca8ae0f02167997caf9863b5048cac185a15e29202b8800b8d9ece343fd701cecea88f9b609396abdb972954039da19e96a416c202ffeb65382135149546cd1f21cba75b4e8beced010001 \\xa2fde9728a35ab1ff7a2be65d6f47e59fbdeefdf901e2d212f206efcf403d34ee63818466fe7ab8eca1a06b0c0750fbb113fd058cc95771dbdd45cc26d05d304 1660128941000000 1660733741000000 1723805741000000 1818413741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-76 \\xcec0faa2fa0c7ba85e442e5b4bd42b7f64c30ecf96efa3af0edc464d718d86be69958f47861cc639f28440252ecd67cb2c7adc6e7b273aef7b09a4ba2b454cfc 1 0 \\x000000010000000000800003c4e4959caeac760a3036929628a728345e9db5e7104e8fcb35417c86f0f127d13568b5378351bfb2909354d1d4a716c7ee86ba38ea20f96089a0bafaa33f64c550e9a55c37e5f5bee43a9d6db5a38702f2ff05e11f24b313f4abd7bcbc0b33aaaa6af21c115b03fad2dcfe72a949746dfc2559a59772f3dd4ac3f394cde82219010001 \\xaf50194318877a76a37b95655e3b8d08ae2931f3f6ec27f14e46691698c3544da5a430fad8ce8c476c33597a3a8e752d3353d1a0c45242d33b3f411daf8b0c0d 1664964941000000 1665569741000000 1728641741000000 1823249741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-77 \\xcff0efb91aa741deee49ffd5fd990ff38a16e26f176df684b4861f218e497702f5b9fdca357aaf8f70af93b69c144fd6b24f8d2278cff1d35565ce1c4eec7091 1 0 \\x000000010000000000800003f2184d6795e8cbc406ce321f99bf4596b40c6fb9abc32d7fd16788e9b6714474631f04b7b0dabeaa975bd758118d0210d46626eeca706333e1b7d8b0786ad26dd0ca59201ddbd4b60c13f2900d1fd98bc7dd3c6f2bab609ed792b85e7e5e12402e13913d576a58de913a5cba7899c86f6d2eaebdd9751af40a752f6189fecd57010001 \\x70a79e20e7117999f13340a20c7b327d41214b96064fb5df2cb7da98534b2b9c16ca6b95849027ea1f94fc73035f71e0df41e59093ae32ff63a74b5a4fcc300e 1665569441000000 1666174241000000 1729246241000000 1823854241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-78 \\xd01084de3210284f6aaa2925067a79776e91e47dd22f420b2a8fa9e6387fe6c34d0533aa8e28620fa67f8624ba7330af964440a66b9850ab66923cb34580f611 1 0 \\x000000010000000000800003bbf909b58f4c4f2d6addd8a89aff9bacc3b169007653023cb06ac4320e979d34a26e07fa7daabce601d370d35cd50943530cd3f8a13158ce40c572e1916bfc064890b80e599704396ec1e6946038032445121e2f0d572446a5742ea45cd8945c4ffb3a3dfb1a8103030191cbe457d10a43cb0acb0f25c31ff890225d9bf49829010001 \\x7788bdfb29ac30fa6fb3c61f4f6b5bc76341df375f9163bbcf7019cb6c781dd584dfcc62ce5ce067a86c3c67aa71bfd01f8104a1f1619ab242d1c871dfbed404 1648643441000000 1649248241000000 1712320241000000 1806928241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-79 \\xd1e829693d38de055198491bd474e14533c442e77fc5d3f9fe80699110f98fc874d991a0e7261c2619d369d96f156716ee2304911af9db9fc9ec9d8fdd045c02 1 0 \\x000000010000000000800003ba5a106cf67700875abeaec9be2a20a9bc74178ff6715a30f89d073b01729de1667ad2b0d36fcf844e4de231867bf9eb582db13e0e06a50636967d5e68d9eea0571f1d0fa301d90339adfd1b7318922465c01db351c2e86ce50ce51b2222ad692fce6fb15575d64ad5406ed95d84079fbf7b9594808e00c6bc1d5c0304b0854b010001 \\x9ba8f589e253d94c832c6e9f6e108e9fdd950ab7bc0ce21c2b6621b97ec4e48cf3a1b37afdc8b4bfa435d2e4c9438cee894e99304432879067dffc324106180c 1662546941000000 1663151741000000 1726223741000000 1820831741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-80 \\xd26ca175812822a5f55ce9ea27aff45e0b86d8efe74ff688651d8dafc96e8fc7445dbc64c1a1af2671d83a84efac1bd5df32c5af17357637eebe7f82ff985923 1 0 \\x000000010000000000800003f0b2c1be391209d37b7cf075db500082bd8cb747eebe79ca7c79913034ba90074a412148dc8e57e77e18083928d55b601343925c77b4c1d0686fb78cae9b89cd6154e6f64a20a9ac1a6cc0d154819721ace8ac4765b3d708f283be2b1b344db0e4bffef2bf1607c411788aeb45a9e070e69845c5b0e5e4a25f397090b9dab1c3010001 \\x031b204b55ba72b1f21f4258ef5141845f2cec036bd0142abdc13d7f87970d5f5993aa03952210aa78fd265789537098f7dc72a0bb24d68f2e9583160c103403 1654083941000000 1654688741000000 1717760741000000 1812368741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-81 \\xd4f40b76aa7c355e7c823084a18ce8054b048e54ff0d2d3650810a6445f16b4130a4139ba7ef753dcc00d787831666e314ea2a3debed52edcb460bc12750cffe 1 0 \\x000000010000000000800003b680d6e966992bf62ce97c3f88e31771bbea6acd77a8121123ca25197f5c04141d066127dc648fb15be3968c2f5e798916ebe2177061aed72bd601320c04bcccf4ff60af69626ea95f61aafd20bcc11bc95bcd6996e3cd153a1e2d1a9e6612f59dd20632a9de670f07b551d128a4c6f6ff14e221bd13e0074cd822f5e928ed05010001 \\xbfa8731a1577c22564051e57bf756402d3ee44759b54d3fe098a0c0e22be18260bcc0b531af35b7f1afe18c5f26438a49cce230a01820a31b9e2c8cc28d4ec07 1648038941000000 1648643741000000 1711715741000000 1806323741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-82 \\xd9c86355a1bc2657aab2f0db2d55a1142f6dc5cd54dbf067b4f4dab849b1cc6ae0ff63a19df3fcfe68bec62212b2c561930bdc81d649f5122d09bf39706f4ee4 1 0 \\x000000010000000000800003a01539ae822944c6b94276ff3306392ae4d21439d02d0adda53e10f3354666d16193a703ca630754b70afebe6300be7917fbaec8aef8e31a4ee8a1324bf22380a44617cf302d85065a8f48e6eafea877be6b5f9bb334d4bea3b1714904040c5ee33570de4a126239ac76fbb76b6c11d558844fb7a5cc7648e213e202a2f726ab010001 \\xa67ef5aedee70770e5c5c2e6c9d9951c0aebfc26aa76ae5ee084d63c9b8410e491a0f7b13b1d03a4b68ed613835a59850fac988cecc4003438d1353af30cdd06 1652874941000000 1653479741000000 1716551741000000 1811159741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-83 \\xdd441f527b37c9957db1229da8abc0de7114209d16473e8509aab28378c35a77a82f4aa991e82ad3181d971ac9002d001f518d00f31188d728cc8becc7b715f2 1 0 \\x000000010000000000800003b904483a6a9586905eb8201eed60691213268ed148f19dabe7217a5782faf397e6517c7d320ad9419f5b37d36c2f1093d627f31c14842c1d62fb941e2c487068c67769a434e79742050574529025ea554e9bbdabbb1e3963e0408a74a4f79706635b7dfa6c9ec0428ccb9e715caf116a168247680760b82b053e41beb28f67b7010001 \\xbfa329a5ee4a218e57b10415286deab71ddafd52f9a79d79a3917b2903b242331b0306d64c0043f2bf96d2a8482d4ef2b41d62dcd2aee07eba0049fd1806ce04 1644411941000000 1645016741000000 1708088741000000 1802696741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-84 \\xde50e3f4d2330f94498f6ab7111ceb5537af9fbb978476e08913c880e7d8b8fa597fa2147653a6384c6e3a17a012fb058d9744cfa0a55bc799c8cc9a90ece4ac 1 0 \\x000000010000000000800003c7e563a598788ba2f143612a8d7a68d744e7f5ba8918f29ff712b220df8a4a9cd8a34d4900624198ba885b469c1db278ad3359474571a66a2db62e33046c5ad5e8e3a231ec9c20cc4c7a44069848c75431aa48dda0208092c84505e42cbc03b9a353ddb4bf7c26b741a37401c8a600ae3fb6aee2321b189de02a5e61d21cfc8b010001 \\xd9682dacd3f1656bfa469cfd25a116b28ee4b35aa42e6ab55696c5a580db0b2023a513942ff65c900101762d9168c56dcac50b40de23b5a708175fb17ae4b608 1650456941000000 1651061741000000 1714133741000000 1808741741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-85 \\xe1e87e57d5935597391be0763c5938700eb660a684704ef26111ea51f34a98420403b3a06deec4314771726ce46c49976dfadae92f507213cd11fef1b10ad0af 1 0 \\x000000010000000000800003af4de3665fb8485269df71c49473cc1953a26fe58ace90cf2fae309e55610ad7c700ac5b49e9bbbca1cebdd9acd38ecd8c0fa5beadbdffaa2cb48bc63de6ac02b56dbe91bc0085c1108098f75c46ef60f2e2eaed6f0f3e53de5d1ad930da861936bf5f21d430ea11f6e09d1ed5a86a4c48a324364bef1240a59d99234b3eba07010001 \\x1bf3ef71502587759cd7996c5544797597a7713e23ace29d8393630d1503ad3464aff239716e1f8cf4df23ad2743b96866b71d40ae16048fa7bd1109e6866305 1649247941000000 1649852741000000 1712924741000000 1807532741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-86 \\xe354e4d32d40693e33be965ead242aba1c35215f98f44cf30a4db8b1f4ed31aa8916f4a250e8e1a2615707554ffd053108ed7c1e9c8117ca86a628fd6ceaf079 1 0 \\x000000010000000000800003e9411b31e89babe5bf9574aeccaace6c5f627491268a7099773dfda32081efdfd6e41a5158a3f79f8f1b8bdb1eae54ad587227b119a2eab86de1dd739af760e757a63cce49acf0f6fe4f96c625ad4f3b1e2ba90482bc28ab1beb027762666f98670e01733232bedf0cab4d22a23364013b897d3f4c7296d5e151f02c54f78e65010001 \\xe47b7001c907f2086c25c1f10d668a5fdfc57177665dea4f09887d9e5b2c349783bb7ce6b7b452b5895db850ae7953e324fec316964791b3d5dbd5524e95e708 1654083941000000 1654688741000000 1717760741000000 1812368741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-87 \\xe42c66f3f7d610b3aeb7066e95212cba6fd66bcb3c9056d36d8cdf5707776d92722980101298e139217a2c1ea63150325a86c52409f112bce75b8e87c85e7c76 1 0 \\x000000010000000000800003c4b06056d71228a0c3e1e3dc850ed0c53f6c58e4fd48e366bae8288601934ddfb51dd6ff0e0119e500b7b483b6526b099611698e24c619c033ffdd40fa28d08e77b03fa6c5f8f344221cfe1a1f9612e95c9a16346f982394e0f783d05438c8e88a5ee997298e27e60bc64f7e9058577e1d25480963efe2e154a37a919056a7d7010001 \\x0991f7390fa79215ff4390c84e3358246bb4de9fc452f0c3349e605081d5e0265f690f608fb9dad83cb6cde0418cd708dc6639d8969f97ca42d511447e66e907 1666778441000000 1667383241000000 1730455241000000 1825063241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-88 \\xe7fcc17d0aa6fd85925280e7fb3a9a9b3a6a5e20a6e5ff7ac53d20df1c33c99403669daee8ab8f7249439b64cd516449a64860e2a153f1cc53a03768746e1671 1 0 \\x000000010000000000800003b414dbe1225834750f4c11fe0d278dacb3aabbe448a5226cc5ff1ad2252615467823b6f645b1e5a8d8b8e01680bfdf3b1b2ce4799b4ec9ff5676d0913fbb3b7098e914c4c437b31aee0fe15b4894dc63436f9aead5ba8588779ee9253f3e2e7d43a4148a3c105d14e938db45639fa70eabb1a6067bd1dfa3ddd26d68da5f656f010001 \\x9f00b773ae31e90003f60fd9428e764b7d457f6ddd6efe74b22ee6917ebfdf7bcfa2ac13cf3913560e90f58bfbdbe6ad284bf22574d6b0fdd109d2d5878d9001 1666778441000000 1667383241000000 1730455241000000 1825063241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-89 \\xe924a22a0fe1165deac19bf8913001d3832422309c9e2faa707e4a5ebdcf13e21407988166474cc9840a6daa072f18123ed96554d34e1217a54f782f02cbf0b1 1 0 \\x000000010000000000800003be3baa5ef5123269365531253bf3d753b2e40a3e348fd2dae52125e06c75fe6e83e0389e5b47bf6f14d6a27c4a0c69054c691b23449506ca927e6fd7b8300a5dde3b1b8486a6230f73639be252f15d094816c5feb1bb3419e2c40d72d51afdad7a0b41b60095ea1072cf5e6028bab7864e13a6938f2b56a8efb620d1025cfb61010001 \\x5f9eaf2dea7898fa57b325117b7e158847932391db48344e81b4b8d1e740539e041f49a61b062c5241e0bd90cad1cf1b4e4cc4352ad4c55a579d7b8ff726fd05 1665569441000000 1666174241000000 1729246241000000 1823854241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-90 \\xed302eb138dc210d128ccc0cd10f7a511c2edc17b7c84e2bc0e6f421730be21394dd958b16e5096a80811192826628c139e61a3de3a094f5ca3c6ab5dcbe3bcf 1 0 \\x000000010000000000800003a4d681d5021b06bf2590ee5f7f629e7ca10dbfa4c87ff89036f1b709c9711a336e553317e09cdfd53ca3fb1b808ec6d73b62cebf10158d766a8c14fac240123b1593cf8edec16a2649f6204de504ef3919aca28e8364f1896bae08c5dbd9ad4bcf29385c78e0fd3696143deacef0f24db69f1160278b7c3c1c798a798bb0ef4d010001 \\xec8dad43233b87298745c9f5fce5a0759b3e9e51a43249b319fcef24b5b04f88146eca0de47460ff32b21fa222a4665f2b1347cd9c4c1d44caf66a2dc6bed20a 1643807441000000 1644412241000000 1707484241000000 1802092241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-91 \\xf0a008a5992ed6dfcb741acad1463c7ba16d29258840e75b96857250ee95f9a493b48ebe2cb59f17ed8393577708ebeaeb599c0f32e72e15ae45ff2cb6b02522 1 0 \\x000000010000000000800003cde2e68abdf46ae611d5a3dca5f09278570b90c3e3ecbe57691d96345b33a55f34b74c41c275574f4939fa6a80c4b2e1cdf9c1ca49ab273dec6cb2d445eadb83a347554adebd087802b599ad3a8aaeaf8cf78679748c1bcfbc656c75b7a3ce889744af16eeea57b914469ca6ef253fce7a1c4fb83ed9babe209efc878224b5b5010001 \\x11b106946ecb3e454af87bbab4891ef524fef097685a4a2ba0f4f3102377af10ee0218a8d15f9640d9cd294bedbc87a8939a4d6e53b04438783b3efd607cc20c 1661942441000000 1662547241000000 1725619241000000 1820227241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-92 \\xf00cdafd2dc9295c36998ca6b5b2bc23c1219508c8cf5b26f4c2fed44db51d178e8cf88518172df9c60d8ca868c5b401649059fcffe7811d0104962b6411622f 1 0 \\x000000010000000000800003c146c0f2f7753bcaaafa9f17c053bedfda603f0d67eaf31926f65b708c108402b63d6bd15d864530c9da7f110eb9cb13d63ce18d90683b19a46104794c70e553d834ad8d43f0e9dbdfc512e57bdb447fb16a3ab965a489290b066993e7b74563cc3a31aa88cd9731686b2976f182fc873a1292e63dfbece0f1a58475c87a7a5f010001 \\x33cc2911d55fd45555e78fc6aaeac367b52ceaf9ca4031125300ecce9febc775d982dca1bc606001b7a0b8dcf309ba53f694d431ca998ea8231017cc656e1e0a 1638366941000000 1638971741000000 1702043741000000 1796651741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-93 \\xf17491fbf5530f616a4d1c81d65e74762f26593164ad78bf4aebb998e914d34ad78566a9091fd4332395189ed250846cf30e0cd06fbc482f1cb9c2f09fee999c 1 0 \\x000000010000000000800003d5406f3ce52783e48b46b45c1a99c90e2c58ddcef17136034c1565652d36141ca1b55f18ac7540fffba638afabac9adfaab814e7cc5e295e431f321a645bc41b969739d1830f4c7c1a4564aaf1ff6d3a9ab72a8aed0ae4fdd010dc58da9fcd45f9561583122bb1a1553ddec1e040bba59aaa4516a5b96621ef27240b7a12caa7010001 \\x3ae80f20c5f4377897a111b0ff7219133300f510687e940b64ac3646bf5e94a00c2f2244848a0e2ee9f658c299f8f49a0fc56922b44dd947c28c0c29b9834801 1638366941000000 1638971741000000 1702043741000000 1796651741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-94 \\xf2043f2f0927766e0855efaa75f114f284961bff6488c1a1f8aa76a4e9af8116cfc728aa33801dac995a77fa012fb4fb5ba2e587257d902e29e2d6b76584ea79 1 0 \\x000000010000000000800003e6e64d6bfb29cb82fa1d6eb480095b3120e82912dd803cc419b1aeb2ec425acba8074a9dc8531b0e6857622bec3552cf08e34f4488ee28af6fb8c9cf50864138644c748ea11d1c31cf32c6774c2abdd500290a2e2d33979f241028325ee58e3a73949bdc735a008a283d1b1927b01c85fa6763a0893c67a78c41277ad79928ab010001 \\x9cea4fc1caa924cfde634c7009614dd1485b813a7777995d8e6011cc7ef368c8895ceec7f51a2f6c582e3fe609f01bd24d8f2059468acd15b1cb61222957dd07 1658919941000000 1659524741000000 1722596741000000 1817204741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-95 \\xf2b80d94320bdc2687e4091bf9d8faf9ebf39371ee7371b9ec0e9ee02efb818aab80b5d914dfe0a9be98f76580c7bc4c5730a2d086a4c27cc0c8ead9574187a4 1 0 \\x000000010000000000800003b01b87068c6021b0a7c140077270cb3b06666c6f7f78b61f210c328cf09fc4c9571ed40495ed43455ad9795e0aacdfa382273fb033d7f4ebe5405000dc66a96c6f4555e0cc43530d13bb4b707520ec3cc405bbc0f7607405e593395f3ae3767a9a5e131f69d86cbe9e5e7cf646008276ab1ba6252cba33885ebd5aae4218f67b010001 \\x48606dfcd00f8982cad2dde215f7d18f3b294de4107bf4a75f256935876d9a3a21cab25891aa0e068d3b8b03b4fdc746d105795f8c40d5fa4a9a0c1ce22d850b 1663755941000000 1664360741000000 1727432741000000 1822040741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-96 \\xf400ddabc1f6c8902fed4ff16d0084585745ce230d7e754439d6e85313ca433be9b102d170dcde0f44dfd57c4aa5c0e360c95e94f7d11d1d14aa0903ebfb852e 1 0 \\x000000010000000000800003c432efc80ecbc72864980a1e96277c5b4625584b1074ea0b45a1efbe7a08080085995ce3c1608e258f6d79628463d94e8e31ad67add09bd71e222a990d93ca7ec56d623cefe540b5869389a3e5f0e329701dabe91c449d438f19843b09e1a5f2e288c8e4c86994e816690d0f6acc6fd125e1853cd0f0af5bde10eb88587428e5010001 \\xe102ace0c467188d782849b5af9d81ca68301140e3a308a1a7bea1c38a3588df6e310b00cb50b30994f1c66b2849f1a2abbeef7a7692d1948eeec99a11438e04 1669196441000000 1669801241000000 1732873241000000 1827481241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-97 \\xf61438bd52fa65ca9a61b58d6d9b95b91ae0fb6dac27cb485eed940204e892458ba050798daa3892fe9df2f7b5cc0af602eebeef915834db218dfb705bedbf14 1 0 \\x000000010000000000800003cb5b5c5b4f6d773b66763bf6961e54a5bb4908777ded45f1de811a3c66d4ccfdc7da8fe833010546487fec0296d1914629cf7bb347baa8d0479b8ddad7aaadeac3b0927510871f6b20b3de1925fcfba5686981d2300c34bde92297ab6389d6925b06dc035245da382f80809f26e2ae14f349575243f0477af525bdbfd0b63129010001 \\xd34048991e7d6066b2941bd1f38cfd4b7fddc2efd408c6a376faa6ae1b6fb960cba6ca4996c82d7823c84d2930110425db834ff3b1f1ae9079397b4a7b76600a 1667382941000000 1667987741000000 1731059741000000 1825667741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-98 \\xf658378e82a5c5262147eb8c7c31a96de5c876bbc4cb84ad9ce604ff10df45a2d226893f4c85518d7a3e95c9c4d5a1a581a3e923364cede1dec2f965c0480629 1 0 \\x000000010000000000800003c5c345c60ea0b65f5d7299a20d862f58360cecfabfa37810ba00775ba7406cefddcfeae46afcb788028a4595301b006e12e0c43d02a3965cbfeaf61348464a0d802983c8de5bedd9ff01da2cc591fbaf3689c947004711879986d9af207671c4263d760cda57fc88438b5177c1615cbbd50fdf15396e17f0b0ac142667423e23010001 \\x3fcfec13b4ebc625d283f09ffef2824f9537aebfb549a4ad7148a2a2723bf2d61866086c9cfec337de867e6bb226241878ffc70c0940d1a9432ac361a31a730d 1647434441000000 1648039241000000 1711111241000000 1805719241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-99 \\xf7202879f05453ad669aceb9aef81e83efb8747789c3c0c151811cab6d452e1a9f8633fa72c1b2b3a62c5dca092cdaa19089aa5be098a2a25523e21bc898cec1 1 0 \\x000000010000000000800003ed1cf191d211a8253dc9430301c0d16b955e2a7b359d14ff107caf8209ccf56e090ee8466d4e892355c485ef3b64a8d9a36d8e4595cd9391b1cda1177a67cc56a92dd8c80343780f8cb38cb3f254df51efdadd970f03690be8955e596972340147c28572fd347eabb8a5da4e84fbe9764ef49d57969d1c667dabce7a58a609dd010001 \\xa3934e6b97877297767cdeca8bee789530a48e27290de02b7b64560db47920cbc29c6634d5fb44ca5e95edd0b803f04e5dfc1c7c6a13aeb5571025f66f73c10f 1661337941000000 1661942741000000 1725014741000000 1819622741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-100 \\xf9b41b0eb6538d512ef00b57f1cd58e31f54b5072ce421ef38e62b424d4d2c0e9858efea08c44a37178f3ab9683578b124000e9ad6d8e0288db4855e5078a647 1 0 \\x000000010000000000800003bef81332a8c5e42c574ad512e2fc6bc65e8a87ae719f15f421e8c973a99eaedf4646cf41aa29c9d1bca828eb755ca3c332b83bb0cdc7df7df7df52cb7ae6e1173b164f64dc5d3860468d717ab1ba723e770746add73aea1d643f32b107f8ad154795175426d5c016e72865f77608bdb4b40b628c7a41033aa2e8c8f5c0c060fb010001 \\x1278d3ea86d064c75352e01d1a8155b64ff5cd8474b80e8038bd144eda1de765c0d11da2841a34e9db0422d7b12ad246c5176867471d316f4c290924af96d60e 1664360441000000 1664965241000000 1728037241000000 1822645241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-101 \\xff788e23802ad112df916c9888733859e1899544e937fbcf5f41092106cf9b8ccbc15c65ec0a2488411722c28a4e3c6de63b1ebc61d6239badb3bc51406424b2 1 0 \\x000000010000000000800003d25d3a13722979e0d57e2eb9ea69c89da8eb2b7f312cea31bd4e73fe295b0c22a0997d357f6e59a9978c7c8a112231efe9e14d48d133938bba4d1d4bc04fad18c8bf9315b5d777ed9916bf123bd5b3bdb1d2304cc8b1783f0196524a9104bf01025864f341c6e0b26bab55e0a6468ca746239a274c04f95a28ed81aa06a25b23010001 \\x53a605ca780d408a4cb39c4634bb7e3dfaea7a6639beb0b7e5d7928418911ee3b76ce75da51f8142f019e6b205f54e86b1aed8b8d781a13bec97862b4262df05 1643202941000000 1643807741000000 1706879741000000 1801487741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-102 \\x01113e85acdc41a111d0f11f8d294570d19c76e869f0610268f787d47eeaea2a70398664c7890be84530ae22eed2eb3d294f4db3bd77aaea4a3289492c9962e2 1 0 \\x000000010000000000800003d2f74e570704185e82bd97ec1baa8605d182ddc4f3eb0165d19ced09a444ba0c6d2ac3ff4f8d3eb93767d5ade9fba174af4f3dbf8af49d0b26edaeb49326908fb2cc95c313a5ef976dbc22214158c3fe4ddaa94ef73e6ec9ba8224455cb4b14359e723e9d11f15f5de62f02a55e694fede355b52a7a6b37e829900f0a5d7106f010001 \\xeb070ddbe4de9bdf5520e8c8547522a1f8ab43e02a21d884514b7635dafefcbfb9f597a195d2d5aa84e7a4ecf055c961ed72a621684e2bcd4bfa7f2e2ae9f302 1665569441000000 1666174241000000 1729246241000000 1823854241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-103 \\x08bda49481a6b09e1ba1f6daf8b61d15972f44bfd39d5e0f1705548c2c3694b93d526136fa9de8a6365999bfc836aac606a43cba19939ddc04e21758dd870741 1 0 \\x000000010000000000800003dac3b0eacc22982cf570719ddc6f78b74e48c6e43067d376a7ef69c7db9ce1fb4ccc1e60da4f8385c89d8f9beb0cbe99cb25bc0bb35606296fc2f4cf484864959b0e93439505b100c14914643c90fdf21a4bf9c7ca9b2b28ddc28f80f60cf023e4d45d0266078b313d4f72c3fad6cb6e6840c4fe4633870878fab3802fbd4909010001 \\xe260e196b00a0bee15474ca7d5d214767a1041857e4827445945408d1f92b20f15bb87c25a532790fca1780c512b5149b16fe0ed4622bd55215e25bdd5c9960f 1660128941000000 1660733741000000 1723805741000000 1818413741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-104 \\x08f13f225288300113233a19ba708767bd13c835ca3dc9e1d6d94a3d2fde9c4f5d812a8425eeae6f734f71a721e4c131f2e53efb9ce95aae872c108643b2fd6b 1 0 \\x000000010000000000800003d338f6a8af5721926bf58242eb3db7479cae5645a781ae0f7e521e6469aad68e6ad12521173d27c62e724211de10e86c531c3983f4a1a878c3d0f166d961e527f6c6b95b3505690b9a461c38e2f81b86921236fe2faf58fcc1589e78d2aef4eb03acb745dc52fd8204846926944ba76161abc4538bb932b8c4bfa58a12a73c51010001 \\x7d1965a4fe2f69d7efa30b632b8147abcbb38a9c0028c02b7f7bb0e1fcfcf677e5a6d14086d34ce0c3f70445bf1d6088cbc0efe52c44b0e86db98d2baef03d05 1653479441000000 1654084241000000 1717156241000000 1811764241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-105 \\x09d540ec32b4208f561dc2a48cf11298a1e993b8461fa7a8bf576199b28c577a376c981d43c0eb758edacc6a4ee89f55621661bfa51bc09ace43e3fe7978a508 1 0 \\x000000010000000000800003ab0185d96c106c9d2cf427fb5453531410c928183e7201625ba9df937c4a9f840f2aca5798897a71ef82baaaad604d4be3f03524eae4dbcd8333e586c5c5df438b2303ba84eb6fc802fe0b439bf1658af55a19ed63008076f39d6766c6c9ecc294514876d2c98462f8074003d31493b68749449a957aa160baefabf139b64dd1010001 \\x7f90b917197ec7d544f20f552a2cc805211ec71f6ce4c651107a9fa9ce0b055293d7009b22ea32726e90ea2211bd62dbc8c2ab04ca9a3802fffe58153d853f0e 1666778441000000 1667383241000000 1730455241000000 1825063241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-106 \\x09d9ed96d67ff752011e466c728e30622a3788f26e9ad3c67c8a520a6f4d346aa53e721a587ec1921ce4510522e509039b6394245c17a9393f0c9df2ade7d54d 1 0 \\x0000000100000000008000039733b7b7d6ed571c24202cd4bff6dd3c12fc9adfa540753b45635126d689a5f7cf01de098afa94985ffe17b0a8514b3bfeca39abcdeb9ffaa45dcdafebf10b01bbcfe9d195619712ef0900ce9cb9efbc9909c2f849173e33f2a237e5dc80df0f5913db392bf09f2dd074c867492c210e17a8e14bdefb0ac7bc9da441b4a64af9010001 \\xaf0af270d7c26eee89d79876d45db3f86962847d0cf95f1c6d87db1853b66059a1a16a78028949fdb6e24335b37a8e49f2c961d3ba7f9dddf72b94b9a3375604 1652270441000000 1652875241000000 1715947241000000 1810555241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-107 \\x09a90e6510cccbf0b14038b48aa04426fa723f250e605c24339831d60f151ffc83e3ba06efa127058fb41ee6db2235ea7974fc692267047190e63770e4872c27 1 0 \\x000000010000000000800003b81a088828ec0c565f0ef9e4ea36537f17e7a8fdb05677a6ab6977947866056cab3625fb7d8c37dbc90727ab16be86f328d6f5464d94e4e2c5f0ecbe3f8ee9c5b7efe9cbf55286a138568ef8dd10f09679fdb2b77d28684e7ebbc0bf54b46249b6b96756b3d6e53d417174a23196e0ccc584f68f025ec3111ac783bf7aa86017010001 \\x2449f456c2a490094043fda692b94a7d8ed0cc07381877f0b598eeda2afda2119fcaca81449cba5d293dc5da0e725a48734a6091b9c1794394d1e676982c1b01 1649247941000000 1649852741000000 1712924741000000 1807532741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-108 \\x0949f79e2d57813c45989206f919562f5f4d86ef00f79eda1a9077df8d428dfa28df295f3a3d238cb4263efae6bb67bdf92364b7c4d055b27a003ed6d19ed1ce 1 0 \\x000000010000000000800003ce0394fa8a3ce66f93ac6c98cc20fc165406a0409dad38e6787485a18f67d5f1791c48b04f592457e035508b80ce6f60408dd47877ffdee8c1fc7527fc870fc8ddf739337e11e096cb7ce650bfe6cd02474613be02486c397f55e1c34563352139a56ffc55fc9692725a2b518833ecd084e65e61e085886a94a170c3dee1668f010001 \\xc4cddd731473d968691ca43888d87ca0f3a1c5e3aab734ee66b4cfdd18aeb7b25f4f50d9a65ba12781d9be0de8dff41d1db4905fdf89c4fbc7dffd5d5c2ca60d 1653479441000000 1654084241000000 1717156241000000 1811764241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-109 \\x0fa59ad7cb57a12e7f29bc3db3314ab85464e845a5f10278e7114ebbda499b5325b62c9341bc6b0ba0655362ef29f19b16b839211f450a09b3f92254ee1bae30 1 0 \\x000000010000000000800003b2fcf2afeb91028b56c7693a9e59c1787d9a00700b067ad70e5e670c647678977b2e0e2bc2ded25d6b27eea255374e54e452a639eaaffa630d40d5ac26aa6c779fcbd8dceef0d83b26a31c282750bf3e74906b9dcd273a1208501f45c118796ac5ddeac8e4929ac96559d1ed417780b6585e791cbaa4b3a06b4611d26c818261010001 \\xe2c649163f0de50ee0ffb0872901bf5a74b30e98416a8ac024067e65344a0a8a7cc8e626a165dc21d044bb981333cb31776fa261ccbd2d1c9ac89ef70afb3d02 1669196441000000 1669801241000000 1732873241000000 1827481241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-110 \\x14195a9da72feb7ae92995a82b136aaca5314ec7974007f3a000a031c94a72b309952ce3140c32ca3a04f04d92f25922f044cd0e948194afc6872027a3587a54 1 0 \\x000000010000000000800003b076d01d2ad99cc038410d6ab19f57bede5552ee2889a445d0025c198242b14e1789a94da5ce82c8abc86915c50c069ec6f5b2a04de660fd69ab947d55e52efcb1a5c6f00fb98247ff92c168fd342fd30a56b1594d4868e947c43690ceb3cae177c8300e5b3a8175e17adab5067f5a0065b4dfaf090c175ac943597a33db607f010001 \\x8ed0eace7b9843b65901fdfbeea4dd62b738e0215e5eed97347c4f82affcb67c5e0ce92e6f0a2e9433d0bf0aa04ff181afa30dd972b615dfaf0a0b4fb9b1c105 1638366941000000 1638971741000000 1702043741000000 1796651741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-111 \\x17599a64fceae9cee6b38ae957858d30a37760723eadfed136b1b75f62f51d8d5376970829ddc81119d7e5eda18859729d1bc04a3bebd0a6b61b1dd1607ec792 1 0 \\x000000010000000000800003d36b4e006ed09eae2198db81592934f8c27891ad695fdbaf050be1134d0f338945eab684eaefe77d0717870d9b1759a64569aacf9f7f471843b7a42966e5cad087d4b069a25126f09ba64acc81b48dfab15dfa7b2ce7a6faaea56e1f7c4e45a7b2bca0013d1d7ddb9bfe6e4ce3fc0ccb5373cc166257b4467c93252c23a1f601010001 \\x0e76e814e86d2a9061492d3ac9470710fd4d53fdb08f5b85c69c2e4d9f30ee5e00edac4e88d08c4a9631ab6847f0ac4b68a49a8d18fa83dbab9bdf79be65040d 1651665941000000 1652270741000000 1715342741000000 1809950741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-112 \\x18a1196715859d9f8837cb791933d21abfc26509eb41a6515cabf8fae16ec7642b849db9c9dcc1fd83b218d6f4eed807ce6868fec7be2f752c6cc2f24f27d384 1 0 \\x000000010000000000800003a01a06896b121953e4bc2f3a56cf4e796c396fa2a2390f9a664a3a504ee0584dc402ea18b210e12ffe6f9fb218f2963173db87a5b06e1e8a711514a246710ae8050e28603dc1875f4f45dc3a8ad282fd9cca1a45008b17235799ea2b2dcebb2b5d458d0ec2879a63df65e8f0889037eeff04044edca2b2ae5d7fac35132e92ff010001 \\x9f9917db091452a87762bed6b633236944c5c8a1b37fcc67c01a2bf370d94d6ccfcd28cfd282f8d5344f77823de41195d93e6643f7105aa0cd496663fd915704 1643807441000000 1644412241000000 1707484241000000 1802092241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-113 \\x1b3d1300f31af1eccf5d65e6673ad80c8c69235784756c0de283975327207931d1218a364aae656eedd603ea80323dfc69934ac67606e19e2674f09584505893 1 0 \\x000000010000000000800003a566e744a3992147dee487df3ae127b3b2fe2dec1adc7ec6872be38f2fc510c3e7abc2b807e3fa7968b979e787bdc8eda4fee625a2b4a9b632ec4a3a3dc7854fab6738f1d3ec8d98215ab85a2770f83d58d49eee43acf0b3d1c67aa7efcb209dcf01a94049945dd2ef1af90ec2f5adcea08e5cfd314805353c78f60f4bbc4cc7010001 \\xe9d8dc081b5d713c68175e50599de3e1162e58b03370f04a12beae651c978e9827f1da4ec63b2256d14a18e859b7a59b3af2f5771d45cb3e85801fca72cb9a0f 1639575941000000 1640180741000000 1703252741000000 1797860741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-114 \\x1b81f84e5a1884dcf7a6a3134cd5a1caf77c71c4b024138c72ed9d5a29293694d844dcb3fa87234f07bfac43ca79c2f82cabf05beb105e2afb79b7ee15939971 1 0 \\x000000010000000000800003de8a64cccc0738e188bb9166aafebb966e2e2c03bf5d50fde77532912035e59f399c0e371ec50d3e361618f079f13e9973b434901ec7d5faeaa2170b7e3be54a2d2c143b2fd1340c2b3959e7b78aa23f96cccae6fa31253e92d1ded424a43b39dedce3099e9053997cc8b5781a5b1d1f13ee90d1331c6f64b7a0f08cea90bf03010001 \\xb83cd2cde0a52a0f2a3a1bd4eb017c3da845cfec638461edee1d0d549123bc910d3f37fcc28613d2f0551ad1907a13e5bb453b890e0215deb317cf127b239703 1669196441000000 1669801241000000 1732873241000000 1827481241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-115 \\x21ddc9309c59fac74874dbd88de01e7354e47c7ae362bbc766fbb1c5653fd2c487c80b4a159b049f4cb3c4fd846f776c1fc06dbebc925bc6f0d01676a33ff74e 1 0 \\x000000010000000000800003c608179d6c8c4b4bf9d05694b4ecc752b04c5976813c41bb94fb54b1a9b6cb13881dc45fb07c702fed395af909b4182cd34861afffae4725bb9586b11e5b4cf037777b4d434fd09f9cf489afae54fe79a73ef68459a254128faacd32d57b363f76b591b95de8fc76866af866e74db13081b14fe50c7e9bf9907b82349f3f8123010001 \\x3b668e8ad2bb3bf0807304a1fbf513d0e312058bed6dac0f908d0e1688e1708432996b85fb85581445f55f25c19d0ec3cb561538233bb405f2b1e6eeb6c5cf0a 1658919941000000 1659524741000000 1722596741000000 1817204741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-116 \\x224575bc60d9ad60ba9ad7257d307210d5cbef98c46a576f0bc7f7afa4c2c7ffdb8d28166e1a2e7ddd267f2b50bfb9eb54e630e13f0fe083bca75453d05853df 1 0 \\x000000010000000000800003c90d034f340ed3f0f20c051cf12afdc51344eace86cd23a29d6353dee6b78d6888da279a52c9ff372da144c6ae8b5442070233602f8db44cb2ea2a1e0fedfd5e322e0493011c0c7c5dbc5d7c0bfc4cc9e93fd3552adc117f1cc5293a26d458acd425fa2af19402c620aac813b2556cab028c8c84cb12e7e25330a59bcd2ce1e7010001 \\x9ad36b1c1de59de3e2c5d1c224d07d7e3c2be057a98f03d9dbb0a8d4d070023d4813690f85a47ce09ef63069a9714841a04ebed02edf9223a35b380e6486cb09 1658315441000000 1658920241000000 1721992241000000 1816600241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-117 \\x2815770f9a8898eb9cc2c1bd80e1c8ea09232f48304a392113179d0f8a039c42c43b208f81a89c564e246ca35c5065c7ecba411587d0a67e64fab75877f61116 1 0 \\x000000010000000000800003cb140f2e35fa4c8d880c746421a342ba47ce50b543ea0ee2acc5110e40ced4a6fe685f0d6938b5a46f8178e2b3acd16b99bd3fc0b91d86b07c027f76690f885d0e029ec658c1503d8b4877c6d21b6ae5d26b511d2079f8178dead403741ffa148d7efc73fe32bb1a239804e1817bcf574fa63341adecf36c579ca7f9f85170a9010001 \\x0359cf9ae726d4abafacfd095e7632079685220fbe6979d453c6476da52c5b512c87fae116819790647ff47bada37a3e81e5fdd0c25c413cc035a81dddc54a03 1646829941000000 1647434741000000 1710506741000000 1805114741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-118 \\x28053d930ab97281fb69edd5dbe5329b3061a1c8dabf0993ce8f35df1036d37b358b1aaaed69a8537ecc31bef4cee3f0ef398d5f8bfd407018c918ef38bd7e38 1 0 \\x000000010000000000800003a948ce9d2270ff90f7d34f47797b8f8323ea5ea44b150f63cb7bc8dd997c1dc6db2109e8dab1b83a3dcf97f84903dd596e08464613c37f6cae7cc9ea245e2ed995a929f1923821a4b837f6ba0f7695d0c03a91bc1ac761575bb1721a8f903cc45be54f040b52c8b82d9c5bd2699e5a11f4a2a82326e352706847dd7f631ac219010001 \\xe7d353f893b790b59bfec03f749f934f0903e2cc7ea862e9477215f8c4040cc70072323ef736924e4b766b685a51ff51c8323b9156dacfb8307c2a0bf0e56008 1657106441000000 1657711241000000 1720783241000000 1815391241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-119 \\x2c2dd80162fc2b91b06de3dbaede27672580af76bd09f56996470742a474db72ae1d02a32cfe2c2c3152557ef52ec7acf56b035cb154c41c133f9093bdb0efc9 1 0 \\x000000010000000000800003a357ffc2e60e712c4f3940652dac5477f35600044d4f3d5d367ba4ce8b9d02957bc37c730ac52905eb3ac163da69a92f6f1bb79e410e72462cdc6d97e4bff9f5fb85448946f44338ef1e0e5daf5344033ec875d5976564233439a8b4aa238f367a40e21bba295c8fb05d16c531a748d8fd09d1c05b617a875afe927c1e8a43c9010001 \\xd0a4ec14f43e302a9b598a4e1759ebb0b27c0d5c6c61de5560c87b94b942acf5b09fa6f287dec990d3d4bdbdd4ae141b92ce409c1064873588c1a3f58e815f0a 1659524441000000 1660129241000000 1723201241000000 1817809241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-120 \\x2d95c72b73b95a6b6638e9aef95e9e4a02c49577f02383f9b3a2555bd8bf406f0a90989dc2bac74531d1b970b258a94148bdfa7602c8cc574a2b8469b09cbc53 1 0 \\x000000010000000000800003d7913782729a9ed5d844b8da3c63479b29a610e9d4fda04e28b1888de2051f70644df7f39f0bc860493a4e77f1ddbc3bb638b16400829778c97d1586ce02097113a55be62995686cc14dd3d14bc2916735edf6d8bb080034812fcdff965360735e30ca86fb7426f523871dbda2dcf1d9f1806624ca9a4d7764a408ae2cd314e1010001 \\xc7f66164b481137adb6cb763fdd043720c481724df9bea4ed45734ba2bad1f9acf64247fb8ca6476f248ffc2b3e54130581957fc48b92232f332e11121985a00 1642598441000000 1643203241000000 1706275241000000 1800883241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-121 \\x2d31354abfd9afbac806997f1f3d06736ffe80a9687a54f26fef1cdce73ea4b4e0c8394f385edc648357d2dd1faf0dd6b060d43cb00be2edb6c3f2a980ca50dc 1 0 \\x000000010000000000800003c7a3a2a63fb98bd1d8db5062c29a8e3e599003e78abec8e8c414f58a80eb5d9c1bd900d3c8510243ef38ab7615bf0d56b780ee1f780dbf397b462faed734f592421c0e619895122002a1bd9eddd9a18bf4cac53bf0c11a7f21abb292ea7a35c74d7fbf1cbbbd9e38b5acfab68fbe5a073e10ca618b3fe4b5a1451043a9b93a73010001 \\x8a96f29c6efe0bd5ad7b02606f9cd942fea0a704f93c0623482da27f529f491c6afeba99e8f60613558c4850ff349b11b312eb66bda4bab65ff8234cb799db0f 1640784941000000 1641389741000000 1704461741000000 1799069741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-122 \\x2ec954fd1a18b41829c33c920f5c7ad405ca2d5805eddcc83fe403f515d0685fb47b5f846048639c9cd32616f94d8d76259ff7afbac995b5e8ad6d63b8eda757 1 0 \\x000000010000000000800003ce6c39db251c0b99827665d78d7cca2e8d281d090ed8881f2764108d5c847e480a23599285c37ac82dfb9ed32154ecf5acb7d7abfbb40ca3e6c98037c3974e47707c4c7e42165abfa1af91a85ce211216425bdd9025186d47d509390953ec12ac01a2f9d326dd9987f67cabe3aa6be0ea03357b9bdb50225dd2368349509e9f5010001 \\x78d907cd85bdfd1fd6c70fdf56b0ac6850e3587ce08c7629d6af60f63dbb2c11477e55db79028c36a686ebc613e7f39919e638803923b779faa3450367d6d30f 1669800941000000 1670405741000000 1733477741000000 1828085741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-123 \\x2f799eb9d9ea466491892d03ff5aa80e8b5f3da2f67e89fa2e3f8cbab20e85cff4a5bbfda0184fc46e30eb6fe47b9643725c07d898d208938b406f86f3273730 1 0 \\x000000010000000000800003b80534908afa43c040ebc3a5669bb48efaa5eca5292d0e589f10588728aa3b0a7d72bf78c5790a21b9e8c4f4e7485c2dd4ddbb2293d77a804312b59dcab8ddbb5f1b96d9df8f6a8e646079233bd795fdcfd764ebd339cae62f0e2850f879e391713ef91d7a2a20a9550774e1a4f5ba48ba9b45c106c1b4b1e8c26c3933f5c9d3010001 \\xd2824b4bb0c85925c13f168650cdd930c2bb5055aa981a78e292d104a717c5e83e2616fe540437f0aa52827bf982e2d010a9e0182a2956d65f0a493bb842ec02 1655292941000000 1655897741000000 1718969741000000 1813577741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-124 \\x30a94c791411ec5614fd2d10051fe20e1dcac7c8a4eec9aa1b885753b30c1215035ec2b98954b32e6826c28820f72149cde966daecae082c3fc464fac0640287 1 0 \\x000000010000000000800003bdea2f91aae1becc08ab7a98c626dcaab532bddda6f3955d341cec7c4442ffc41412ccfd19ef174c8ef1f7261ccb69d9f387fe92daa5dbdea362cc674c5e640d5c3578c75940ecf3f1231c85950d2cd274dbc65b3e4d76a2774d91b9ebbc416ca66889da78cfc51528656971df3fb36fc857b0d4066f799774eb26c22851c9e5010001 \\xc2100ff5124cf366f12cd02aa375d63cdcc2e3b065b3eb484ff12448282fca3dd16f4554b612aa8965b84d5b2dd8309cd12a8e017e77d033fada8356dd413a0d 1638971441000000 1639576241000000 1702648241000000 1797256241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-125 \\x30b5c7633139a82622127dc17b345c71f3387bdd7b9a379cdaa0d63018059693a87cdda12111eef250a126c49988d96ab9bf9846e6cc541c67984ebd36932b6b 1 0 \\x000000010000000000800003d7f833d11af1963f2e135034fa1eee75a987be9097f3c8dc0ad58d317f82526af9b48aab87480d5d783d57edcae2c185b77f6d4e8572fbf2ddf34d14e3b43e4233fc4bb57dbfc18b235d231aa3a1b0d5e7cdcc6fe18596dd6dd395e54fd323558d0009d0d7ddc3d443b72b37707e4b4301c6a94bb9096ea3d728fe25bdf0d1d1010001 \\x2fe71d20908cccfeed7ce8e4603e09c0398e2c28b42e8316b47ecb9de2514d824d878bf4a3e53c765c8732677fb7c9b683bd3b6f1b87c15d4b55cb18f0c4a903 1651061441000000 1651666241000000 1714738241000000 1809346241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-126 \\x33b5e709a3c2790254089ee44a1a29b9d638215aeda0c8e584624871d6174ad02e7d62b319122bb1b82a5e61949a8656fe588e76679736fecb826a385c65caa6 1 0 \\x000000010000000000800003df8da6792897fe6bb1a5424389171ea996f4c437ddda94cbe373fae32ddabdb31f4c7ec8c218bcfe5c2f8df99797211da2d523377fc8f3880356b0bd5802268e34eba6935361ace11cbdb62f6a27aa6decd4db4140b2435d97712ac3b08d018c7d9a19236b2040c0d7be89bbf19e21e17198fb15d0519db9ca58737861e9b865010001 \\x21938ec8111009021de9e420d30f93a1747af5cb03e630d6212c9ad735797ef756ade5e70629584b9071c9c7075ea6a46e62e88357ebc5a17e2d35561c024f07 1656501941000000 1657106741000000 1720178741000000 1814786741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-127 \\x35950cdd932aedfe29f77ff5b91a97d5dd1538abb6f6bfbd5b07dee7f12e47eb3d2aca6e1246254ac1010f3f351e016a5c427498c3f3bb45d2e6de139728d5c2 1 0 \\x000000010000000000800003a05686677889375670c88d03aa932ce877ca85424c731614dcf4dcb4fd9a3a8d6d6b037ea3e528b0f463f3fe0e5f49c9f9ac18884c9f84eeba585ac8f1257095ac8ae9b3c8a318f6d12e58039a587ee42c572474f807e29cb4b2938e54f73d644ec88d1ea7437dd24a23dee543bbf1c3b24be93977bea0460b660ff73ff951f9010001 \\x09d616f4ffd890e811b0ee1f58c6aa7d8ff8c29eabbbb8f6ae6fc70936f056b6c9696415ebb08ca342df8de1d0da18302e431a040ca1528ad3b9bbe328713607 1661942441000000 1662547241000000 1725619241000000 1820227241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-128 \\x36b1a1d2d2e5965d7b18b1185baec63cf2fcf65988d5b419c3bbcdc05dcbb20c32359329539fbcce95b42228a0f5c7ef9fd79ab040aacb4c7b14816f0b9e3282 1 0 \\x000000010000000000800003c887ffaa642b8a94560313fee1e7d55841bb20f5d9f931f011e4dea3eaffcc170cbce57c4afc99f527de5704bc6a67dbcd7f52f172119e634d758ddc1b44f6a00da3afd28fec68b3dd0574e726536bcd45e9bde28ed8f32c4453dab48b7388e8bde6e33b16f955948a4725cb44caee7e9d7855230c22ebe5c9bb061c9ae78717010001 \\x9e353631e996fdd7fd3e0a30bda433634351af7cb54576a157089145892b1498649dc61f5d9582fa15f8e57ef1d55727d29f983b1f40ae705faf02398a036f09 1658919941000000 1659524741000000 1722596741000000 1817204741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-129 \\x370d3dd1919d331d9852cad3a07913e19aaf2f63b5286297733a13173b6ff117c82295a30b2a97ea614a023ce39d872916eaa95aa6fa680f81b828a44af6cf72 1 0 \\x000000010000000000800003c68f70f52573ed482189bf0283200bd94fcad3a8053f5bd2f0635b82f82836344d60af774e2165bdca3dd930310c533f93f4b47ad8a943cce306f1cd78af8446c01a70b0ef1b037b86364487394cb588c22deb1d4cdbd89eb09810fa4fcbe6e04ae5fe7ed8626e2d44689199de2eb78ac518ffe27c74d3c7d46c57216d27c767010001 \\xdb1dd7d362d590ef092a721cf18ba40a777fdead47e4f2010d1234f6fb6d3df206f6350f44951d62fcd653a5165f16c2263869d30f17d4d4b4287e5831a27e00 1647434441000000 1648039241000000 1711111241000000 1805719241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-130 \\x3ad1453666f485a86b632b819beae0f06fc98d099106a490c90042d5b14a2fbc4ff30173d046165b082080770130c51c313fc57b7606682bd5e48e7873edf6dd 1 0 \\x000000010000000000800003ba55d88e8e5fa877235399e837e731f6c4b654565c2e0d909b583233b4f98e1f9efbc37cc2889a18fa1438cf61011985c3c3b39759b4f14984e18a7dbc54d9dd110b133dd1814a55cd3aea881e9e90e0e7ccc479bf760aad46fa4eb1f5a96ff305cc2155e910285891eae913251f25b1980be17ee3ad5f68ffd40558e27c4eab010001 \\x1083788e63602d39298c792fa04894dfffb2e18c36ae2ca2b614a7dfe1dda650e3c21e2440cb594c15dcd0f2ab6c176f0319a8ece166cb40f8d8b8b638cb2b0a 1649247941000000 1649852741000000 1712924741000000 1807532741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-131 \\x3b515753d8186beccf418dd192148102cf931845ab50f8700025888ace64fa6a0bb5fe91ed7a4045f19f91973bb8ad356281cca08054edc4ee35bd58b22c2dfd 1 0 \\x000000010000000000800003cbf8d5882735f53636360136f00ef3276619c6502275a90e76706a438f0eb733c07f9121af3719e101f98e6e0a1af52e45c22fcfe9159819041cea1e24a180584e248c2c0b36bfb6c5b683d63752b3741efbe71c30558f515536510deb375f49439648ac19a14afd36e7e69246afe9682499edfb876df395f03a4b7d714af56b010001 \\xda7cb3e4fef885e783923980e058edabfad5708a18a3ea37b984801bdb0cd09a0f045ccc89835b6082ae60790358f63e9b4b3c280d2f125c645dac7b94859b0a 1642598441000000 1643203241000000 1706275241000000 1800883241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-132 \\x3f0ded1ad7619a60d5e0145d6f11eaf696e6e3fec9aa35bed54d3d8ffd292dcd723d241a4cb39bace82965012227dd2389e6100933aafadaf3d16054461707eb 1 0 \\x000000010000000000800003c32db69146cfb87a18f290253d9201b4a92bbb3bb45944a074f5995c32c909be4f84570e3522b150a5e724c88da0cdbfbf716ca09e6a23307d43fc22cbbb21af2984cd3d15554f14d68c20adf8098b88aa3481e3d32ecc1c0efd52c72413c0c1723656d4f844ab1700ccf934900731666f9a0eb30bd4c38245c3158f471eba59010001 \\x0935739cdfb8c6d269fbda59e77c0730202696793c828830fdbac354f5f2567a486183eaa9f746fef121e9de80f0e5c767efe7cc431f944fee63e98675655509 1667987441000000 1668592241000000 1731664241000000 1826272241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-133 \\x49619f594ada81c72c83eda8d1cf7ddf2d5255d2e863e2d34f946b51630780322a35df138f924fdf5f150a65aed0e899f4bdd436419e95ca883ba7fb2a87dd8d 1 0 \\x000000010000000000800003cad97786bd629af02809c94602f50bed531d96465ca623a7fcb51b511536c5d555e0d18aad8b377efacd8461e47599d3890a02f9d660257992a40a1c66811b225a624f9ee37a3dc28a5de9f36d44855124dc2681af5906cfd2eadefb18071e6e52449ca4bd8057319238a8bda3d153db154719b3bd36bea34fc76f5f438105b5010001 \\xa8eca51a75475b0055a842614a15385140d69f58423c378f77c7ae8a16a42cd22f5f700050df8a14f8817d96aba1c6809471064df9c4c69b7d7b688d3104af01 1655292941000000 1655897741000000 1718969741000000 1813577741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-134 \\x58fd5188f08293723f219f76899168a1af58daa2d0a276718da326c53cd4ac181e2033a1e46811d1307baffc829dd1813a88f939667a4c0dab83601abf20feba 1 0 \\x000000010000000000800003c49d9a1a767ac59055164c98973c60327391f3c834e2e95ed296783e616f46e21480e871caf4720eb691bc6ed9b41d95f3f04e675652758ef24cc0bd0aca9cbc389701249654b5de56c43c0680f9d0ea5f98138890a4ff67d5d40159e89d7aa462bfc64f876fdc17077d352ea87b0688831f080e1b3b54580cbf656f6f61fe37010001 \\xf74668b957450b854f53c1ecf6f75c1a266bd2a437005eee38e8791554430ebbc3bba97ca6db7c8c638af46e6412ae2cf28bb9931f1fb6bc992131f990bae703 1638366941000000 1638971741000000 1702043741000000 1796651741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-135 \\x593d45191811123aed5f53f43b677faed3e3582c20d01f4d8df557454754752ff6ae82734c1c284d5b49632f52d416e29e3bd20486575370027ef00a0232e973 1 0 \\x0000000100000000008000039d3eeeac1d8c3d8b22872791a0c33990d2577c9119a40746d8306d108043c427803f0cce438ed45d53d79a3e361d398a85b3a7d979bb93e08405a5c436b2946e07d64b536c968ec3d368073e4d86d29342d49c93a30381ffcafd2776049f8892aa44c590bb8b2bfd47f791ef16414994e5e345ec579548db79f7d01f78be064d010001 \\xd2d8615ff3640abb605c22f6adaeb654cb377a051e074ae737ba60c7f6950b405b13809307f0196dfca75eeefcfae413c18ff150be9eabd2f8f205e8143e6602 1643202941000000 1643807741000000 1706879741000000 1801487741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-136 \\x599dbd11dd874a654620dbfc4cc8127eae7714362ae05a8e2352d986805c7308e369685a24e59630833ef6be1d48bea4b1d76e608a1e35440cf70b0357e1f76c 1 0 \\x000000010000000000800003eb1ed568791f7da3ea7b6a49c6119b5d6c9af6fbafbe9b87338c19d964d957fd731e22c50bc849fafb1c330a0f6d1150a7da698c03433c3fc3ec0dbbd99b1ab6e39dfc3f488c0bb4266f9d4b7dcae7e7b6792da7458e343dbb1378a36ed2cdcd62c323d242d0db4ec39b6ccad078e0829c12ff696458e08ab572b02df96a6075010001 \\x6ffa5b06c722d9272f6a4d03ceefafda0754ab3cbb12fe4f4788ef9b3c2bcc6455795da2782c53d3346f7ccf845d880608424e43477043d42ae82bf6bbac2806 1661337941000000 1661942741000000 1725014741000000 1819622741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-137 \\x5d51d9ce454ae3ea07f50ffcaa92f1ab8ed18605a4b547ed1b792d5a6dcb92477eb7dc21b84a6ba76a8299fe89b25a9f5f58a21074a8f154760ab31ae936aa9d 1 0 \\x000000010000000000800003dce2784fb6e41a85d7197f3e0949a20f03d3b6b01aa5ff41383210c2396c5fb1b552c7740991ea02d2639f43e6022e73657e362c25f1f13ddca3d95adcb89e79114652cdd86f8444d4a8da930df86bbf492f2529c3dc597b3d6d1e969c97cd0b79b9cdcd3c2501bb27671b45aa6b565763a8a57a4e07020b91cada8537f5e397010001 \\x9cb2f223ff3e1e6ba220976b41e126556437f44d7c7f5a6a7105486b14c5d106208b810a3563319b13d117e9afdd2a50a7ae6de40eebfb63171996ea0973e005 1657710941000000 1658315741000000 1721387741000000 1815995741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-138 \\x6209b42bde4487a219013a4e1e6651a4d05252e7fba81218d0fe222c05ad1bcf52f8080e534e83df5be1065b498c3562b5e503fa57c6b789f0dfc7e24bc5cf05 1 0 \\x000000010000000000800003dbf6a5bbc318754b949acf4c5bf00d953654f8c499b2d69245471eef2e94f075129f3e1fceb258da882f45b75b168343747493a8362d0de810bface3302c19da2bb4145a17c8836ff58f86c1fc86ec4b805f99a9fc04f4c5604a9a8df7eb08a96b292fc768f5d31b099208e28fd5f1fcdec2c1b976b6617e9163a7151c98cc8b010001 \\x24506265ca71f23bb7146a28f1086789f3348ef8e1920e6e27ab2c45164260bd9a46f7ffc1fb8591f450810172f4d0197c899b4dc6640515c41afda21b573906 1643807441000000 1644412241000000 1707484241000000 1802092241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-139 \\x63855806bf2e743384eeb67de2468b5d3e71598b44f7b591c86c549d909e00f0ccbf0547d4a41dbab43c2e9a9cdc4cde555c0a75945be6a5c410a46e6438e160 1 0 \\x000000010000000000800003c3d382b4a070e3c338a8565de7196a5c3dc58908106290c0fd2e73980676e5cd32e244527ffa520a5022e38836f8e0ba8b299ba1737fb4260aa49153058af15820c41c6eb67f0686624a0b315bac8874a785c5614dd15201e93c02f1c495e58853d0aa52abbf676fcd4f3ba61c81ec4753c21dea9df43b94b9d2ec5055659865010001 \\x768631294ee72632afdd45c7808ea6785bd445cade523364724034c03afebfa738c908234e62ed7f93d6f8df550dff91ee5f04d9b8e711595090196cbed50c02 1649852441000000 1650457241000000 1713529241000000 1808137241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-140 \\x66c1830477b7d21c2d2710fdab7cf94982a981c4be4ecb2b41ba1818004272f170fc1e1d3f7dbbffd6437fe7d88988726a70948f98b7134869e558c1036a701f 1 0 \\x000000010000000000800003bfe5165cd0eca1c8e4439c54a428187d0ef8e689fb1011e1a64432d777b9ed8452f45147e0397fc2067414dcf0b9a89d0530f54e72190c8f81cc2f19f71099d6870a4be5a09a946c98ddeefbac6da421f4dae46fb98261f1fcdca5ddc1fe1cf469860406581ab2b0e85847242b1ee8f06ceb6bab247f98a0a6ff1a2bb49b0271010001 \\x0a02d908ad210e446827e3e68af14e23186374804b875d3d42d88449e31586e6445d80e44293b29b2e3a750e0df753245de5413e1e819c31fc330a97b9385c01 1660733441000000 1661338241000000 1724410241000000 1819018241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-141 \\x68ad641e8e235be3c357a89bd71c7950af697436393a376341cf7552b2a9569ab2c9be145139cd4269fed053ff348fcc1dd4e46237fbe506d4f56a74862b0f3c 1 0 \\x000000010000000000800003c314c4fc6a6a4b326c930a4d0234ed64289e8c6dc41cefccfad95ded507e456021c486401a48c5e336cb838408ff64e8b8360c2c2fd150b8946ff61745fdc95a235d67ae102def2eb426cc152f5120bca8ba01e97a1233b2b8318bdf935fcceb35b87847d9788e503dcfad375d9a8904cefb6ac79f51dde08274cb741b33aa13010001 \\x71375cf583c82a66b91ad8ab9f537be0d4e834cac37656488176af9d62e320a574dc364e82dd6b2e029580aa450d7c3e884a4f327dd82e5bb983ab74d2bcef06 1657106441000000 1657711241000000 1720783241000000 1815391241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-142 \\x6891fb81c10769070cd23a0e5a2c1d3ee8c1d0471fcee7ae191382fafd47014cb9c5b39fda80781e382b8fe6d6a1947947be6ae00755a5fffebe5555998e72db 1 0 \\x000000010000000000800003c576fad783a192d6843126c40249ee686fc65660ead9eff5ed8df4d0d114fc095bbed19edd14a8e5ddf3fd5bad9807b669c2d29707a1802a61b8f52ca1c37b438afc1548dfea20ad7dfd3aac2f76608a3ae8b5313a9b0075bc894247a424263680c20dec6729a251904f6a17dc5537ca7befa53f832b4affedc02e404bceec19010001 \\x30eed729ca247498ea6ae865203ed59c11cf3d19ee17123e6040b10fdf9ed8340fa0b11aa6cfc77fb5737c1efff773a1bea2258a55b710a1173238c4ff889d0f 1658315441000000 1658920241000000 1721992241000000 1816600241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-143 \\x6b19c00bf5aacf95b2a6fac41d8f510e0f74bac2e075606d2e109871423c6ee06a816ca26edce9ca0160348b61ec71c0a9df3b34aea56a47571a191692030af8 1 0 \\x000000010000000000800003e0a8d879c9ef2ae0da6938debd7c192ad7b7a58bb8aafe79c84a5bf33f5a42e93f2e57a6399854cd9871ee869c203d3e304f70ace4ee16033e1bb5e8108c8f00b00e32f899a46062f93af0f66825eeac7cddc5041ae41538c55bf0bc407c37cde98be7d5d332e1339cbe3ddb915c8a268f79c5eb00d28519a2956fd857ab81f1010001 \\x42f080701d35d7ed44718b76ebcc57a05f1e26446ede8b6257db8fe4a8ea156a28c66227d0aa46eb9067f42e2b69f5ef999ded3cebb839e13becaa8c5ccf8104 1641993941000000 1642598741000000 1705670741000000 1800278741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-144 \\x6f3d8b4e6d85d785549b770e30b4c7f98ec4e1a141902c306ead89b6773a62ffc1f9b04b1b70b6f898ab98be624d0dc6359f610c875941ac7f56d65c2e9b682b 1 0 \\x000000010000000000800003ebc940d9f81414933c01159fa19fe388903638e499cba06ef923f39a7e95e80d5405e3930c968809da70ce137804dafca7137e162586a0e50a42e33b8aa72d177e1693895ad68b747a503dadc9949541e5bd60c2af78f31263c54aa222dfd6c36bc16a691ff1fac834b72ae624501492192065dbd059cbd601db8d421f0e0903010001 \\x1900ebaef0fb540ed78f9e932c757c388d30ff1a2a497c914b29957b4ef996853416b15917a9bd76e5a4566c2b18bee095118b718fa1fc4b3416f706930e3d0e 1663755941000000 1664360741000000 1727432741000000 1822040741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-145 \\x7159ce3981f6342cc35fd31d57409be19426e230363c0704a70df15113af424459ea26893a21d5b72ebe477728bdcbc61defd67cc664c3b455f2c7f33f5c822a 1 0 \\x000000010000000000800003b9a8e788e85e3f5ee0a05d03122f322a22c571fc55f13f2ad4dec97bf3e94bf0de13cd681f7d31d830cb0c4faddc6d22dd2e870056a80a0a687b05d834f4f3b9caed0a885bd6f2418d3ec97b271865c9f5c190d02e840d9aaf520172911f6f61599a7c3485f9386c423e10e4b09fa3a9b3ec7327c21d8c6031dbb93efc171ead010001 \\x2600ae9eee2a1b0219f24b89ab4e9c0c98c4c96e097fcb11e38e1de8fcd25e54c301edf53a54e4197cd0b73e8684de2898db83e07d47288603a7642012671605 1638971441000000 1639576241000000 1702648241000000 1797256241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-146 \\x73a137178f211cf0f01b016173bea4316824a606b27db77d408b3e1577cf30f38e978eafd0834a6f208588942d52bd364aa0dd231b1ec2e147429a062573edfe 1 0 \\x000000010000000000800003bae51bb90a2419dbbe5d39f5c91c7c0101d11a40080c34b125c61191e248e29a41297472b9ddf9b27b9c00b7494e57d66a68e14fd012ece76b3942c77ddc446029e1bcd39655457d5105e81d12c7304e910eaf7f248a216aa2110a21a880fc6c8582f85652a59ac2c55a2bd6ad292e6d276541d28c8a9ab74ef38d616a25598d010001 \\x48bf50752318fb88b2bfbbf99e61fb7ef2b38b421f328c5fa66624a06e1c835fe3a2f3040a03b55a3e52b38402c008f4a5e563dde2a30c9ecd8ce4c3fa20510c 1660733441000000 1661338241000000 1724410241000000 1819018241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-147 \\x7791ce344be22d0b65994a385fd2fc6d33446abaa305b70423d85e35232eb1353f3046b6d9fb8168fc296e2a3e938a9e296459e95d66f8535735def5d3b4dc1f 1 0 \\x000000010000000000800003d983bee847e35c9d056fea984dfc572c93f4d5de21c800cfc069438668e0fd2922be45683dff86e0c528bb299a0e1cbd839d8c847ce094ae3696a1093357a9c53854810ac8761a83d95b950735777a0723accc80a8efd5f93ea67ba5068689f8a1bc5868e7e7171d075ddf4a9707544716bca5e2b8f08aec6ca80b6137453bd3010001 \\x6e7ef5bea3ef86902928e55992b0ff522d54c6fd7507e659792b4cba599d26848c18a50c4523c06922ab6dc78e23bfd973aff679bdbe8a6e3abb925f2309400c 1651665941000000 1652270741000000 1715342741000000 1809950741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-148 \\x77bd70b9a21caa0078a835fe6aed7de661de15e5f8078022e544828321ba5f5f74ffccf37bb1535ad4163b4141b81ef4059b65c201ac0560e3a03c29e29f3b65 1 0 \\x000000010000000000800003c5eb0dee593b850e357fb8d9cbb90ebe0f0ed92824866d4576b640d2389bb7acf67a7bbb9c821f62308465e76a518e71adc8805e0d6fd4b3fd6e408f0299e2ab853381b34c8fe243d1645797342eb909a17a026ea26f06037b73d91f854a962dc1ec621efb69f76fc6d74744fbef5f3bee49887fa6ab5522ab2f39fd67216221010001 \\x0b3f86575c924fc087befb22e11a57be72d239b2ab969b6c53730592b0643563d2d6f6b15ac04de7c0fb8ce814f58e3918ea6a0337bfff4f07f228371a477e0e 1664360441000000 1664965241000000 1728037241000000 1822645241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-149 \\x784d0ad5b14a1283d879a120d6ebeb7562f486aa243cf9cacbad3e66530914b4de95a367126e18fe4dbb29335a7b23e5c5190aab9e6672dd437113eecc2e0b67 1 0 \\x000000010000000000800003a98efa89c18267858c6635dd9afe198c57b7d77e530a7d4ac0d458d401405ba2e11b428c65e15f812c5a3085f1c6f0685e8012419dad68ec2a6b6a445db5a8396069b4d3516a18a9de5793d4c8cba0726df676cadb08a6ca464460ccc0d47d7eeda3fca4e08b30b6808d55944fb0232f61c866b0e8abc88556532da1b6c09b33010001 \\xf8af19e2e3518703fa5cce75e4d8451826783d3e5d5343914d749c4e71223c6f8942988f04e673d88d8663dfe37e914cac73ab6ed09716327f9aa822c130990a 1666778441000000 1667383241000000 1730455241000000 1825063241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-150 \\x7ba558fe4c0be652c59e665d68fa338b679679f812d11d73b375807ca64865ce77f23aa7bf0d456d16145c3d5acb272068af14fdd85a2c85098d98f205560b07 1 0 \\x000000010000000000800003dd3625fdb63691fab6c1efa71606e4bc2a825ed6a981cc7d565406c077dd45c786972c7c9eea752aaf3f4972465bec1a6d4b8948bc875bcb2147c17957c4b0f075c00986bdf6d2f25dab7de5d496f314ae8ca38909741c2b207965ccc0a9ed373209361ba73249f38ade71944774e7f1fdd0a9c6d1ea1ec8457ac11b8b9b7d4d010001 \\x7e76c5b4b44e65301e19df59578f7039e7cfd299e3b87885122afc2780cb105c4a5512be2e82b0421fc2e053a0980d2f3cd2a0e7787ea6213cb8d4e7b6d6f903 1667987441000000 1668592241000000 1731664241000000 1826272241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-151 \\x7e19ffb6307048182359fa0a3b73193812f4d3ee3f3e480319fa1c8f6787fbd19467582749836224299b65eae6bc71c3d250809ea0a88f8cd1c06a40260e80ef 1 0 \\x000000010000000000800003afd129f64afc4a501ec98f160ff939d4ded8b16310c22be53200a82b901fdcb4f436bcc78488bb676c6d004e5eea991c4d7076e2993776a73ce0d0c2f3dab2a1cedf9e7dd45ce433860a77f4c8f628d28e4d82190b6e81c5be3b11a71331edc9eb6ec703dd6a2f0e29ac73a3c3ec22d260fc2259ed49604f6e75247fafcad1b5010001 \\x610368c91216340fdd799acb264a2671217f02fa027523e87c249334184d41b44ec3a986830efcfc8e04d515c0788051db44163ee6bcc7092965a987781f3f0a 1644411941000000 1645016741000000 1708088741000000 1802696741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-152 \\x7fad8f92227c26ae312e24420e4db272fef8d11fe931c9f02b739b4a41e12aca09769c878a54af03dae3d9fef8970f4075c217f7bf1bd02026483aec943f2d6d 1 0 \\x000000010000000000800003cc8ee18a5fe5ea31a6fd736a1a34a47f151df65b8d07646eb038c8774a5dd1662981b8ed2a7a6d81f8562d6dda71a3c7c3b7983be7da6818582b6b0b472c50149900fbc9dee04c99155154580e72011f04709d3c3dad4687fc6e41cb06187ab14c9025e86691f29e18f732b51db6143b12aa36895c042d12362926b6f99498a5010001 \\x7b063974c35d1f0a7d8bbede65f5411051e361956c8467995af7cd3cac0184a1cd53920815c3e0eae12607111a76b5c0ed60b90fa709bf0942c3ce63c855740b 1646829941000000 1647434741000000 1710506741000000 1805114741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-153 \\x8091f37a61bc827faf7677c22c58082cc128a831e48025dd7766974a32430ebbefae0e4509b9bf436a93fc404d7eeaafd85dcb33b0b4f7781bc2420f230d15bb 1 0 \\x000000010000000000800003c7ce86eaba04119bcf880fce478664e2ec383274435e5ebc13c81246bcfda5dcd2a094757f1f64db69a973e9ef294e8a975c2ea00770228fd0d280932e32f83070d2a08455dff0f22e3c886560685d4ebcef2f097d058cbc45341557b65817edd15030bcfc520b1d6b887a5cf619b38d56d34abe38d5ac59d6def5a693df7aeb010001 \\x81b239d3a4a6dcebc00b5c32a986ea07879a0ed0ab11f3a3ebc720d3089a042b7c0b49949c714fcf1a06fb960df8f6e40632eb95f8d6677fbfe8eea3e75da20d 1667382941000000 1667987741000000 1731059741000000 1825667741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-154 \\x821d1fb124c698136d91da3cc26fb07b7c865bed40a33b05f6cea82b73e909ab091c041a5032a470d16b8301b6d96db0617294437b06c1b17221aa1995689dbf 1 0 \\x000000010000000000800003d820b03e898c16d647431389fab065b49f923e6c41d8f52e51dbd6500c560dd58a7675af5fc512aeb6a58535756319b51cf9f33df06b79238a1f8c432b0e4457c1cdd43b481d953bdbe5326b180945d168f4cdda6ec2203d88e4957eeb06eae011874ca8294b5a5853ce4fac1a064719e91764345c3068233a6cd808db17c9c7010001 \\x484db55d35e86d5b70303803eca0a2342741d9d2a1392a0c64151dd584d97991e69e3e80a9b47c4e88fef116d64dc1077ba7fbcfe0baa50e3df32c0c4daf4807 1661942441000000 1662547241000000 1725619241000000 1820227241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-155 \\x83a11380242b159557f378d56bed6423ee3c731baa6d0c6255628ef57820ef15554675aa3956659c3450969cd631838acdddf265a12cef9e1a37eca193d23979 1 0 \\x000000010000000000800003c69a2021cbe2e9c1908a9b5c226c0cc88f4fedfb7c0b125075b3e8d6419ca491fca9a35222e3017e4495ade8e24a93cb51f8ab33c353bcf5ffbe3eac1d88ad52833473f85effba005bc68cdd5b88eeef645cad1547409648689fc4137e31ae17c8355bbfe5e81662cd4a146f59c8e37072863a5523314f8f8de1d3f6e5e9ae33010001 \\x68a25275c8e1e1181749eae9a4c837243f4884cd83466d2ab78a19a598632796c3a724ccb6218a2adba3563aafb3975f1d643b87126ce0362d67ab98c2b91f0a 1649247941000000 1649852741000000 1712924741000000 1807532741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-156 \\x86f9a1dd386c3b3188cb70c46cd22ea198c0e35afe1a54481807bc04bab2821eba6a504ad065c8087162db679c14a3998cf74f122292afbc6e0cdfd544cde36a 1 0 \\x000000010000000000800003eb89a2f2b8233693b412b667b78a36f6bbfcef6a28b3b03a18b1f96d93affef64da84e22483f5884bef8deb074b5155fdfaeb345825d58d3c6c0db215b04b0467a06f59f349d287ebb66f604f9a4aa8cb8ed847e25c6dd839faf4f3cf4106b74978e13fca904e1101192725d834e363c1a343019710f9d4a2a91bcde0932c5db010001 \\xe840123a4b2fce59dab8f9da61499ac5a1e9b46bdcca7fc04c319e3f62976216f9f2a4c9f69d7414687159fb47be930cc10288b02c61b3b7fdfba7401d0bd00e 1649852441000000 1650457241000000 1713529241000000 1808137241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-157 \\x88a943969fc8d3acd7519c3c0c657e72a8db913a7508e8c5d51a568d09f30d661e359bbd1a2486f21af06c60eef27142a7b031ec0cfbd146fd144b6b6c3e2a90 1 0 \\x000000010000000000800003a02548d57c0d4fd25641f24c3576e24adea540744f2d7bf58d1a04721a26d70f57be1351f31d268b8d2f8728f2668f787a3e553f4e688e454f85b0985e2facafe2d934280df06feca72276e328d1389fc266a64a24e460067de7727e4564ec623959823f9fafa506005ac4427db53e52a51d26c5ffb0c98c24688bc66ef6ed17010001 \\x88037cbf29f5075b6e43c1ac86d880c93df99ee9e9c0c9d3161bd9b87f7071d29408603612194572464deca10ca483b3b29e61dfe05c48a9db886712ca56f604 1659524441000000 1660129241000000 1723201241000000 1817809241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-158 \\x897984ddf6353decda4af85e6641dbb3b80dcb2276e5000150260a6210c4dabb0bbde7b2e30ae772d3fc840bbdbbcc2c5a26be3d566f952e6b17a24a5656fb81 1 0 \\x000000010000000000800003cf634a369bb54b06d64dd391e187840afb1322be5cb73a21dd8a2af98c915027d4c29f71d5e33fd95a88bda960557812343d37365a621999831b691f7d1d86fadbc797c746f398e5c2f314d0bff8ff6aeb22868c2567e934e9c737e0cfb585c999451ba2d43d1f53c31d1884baa333084c004c92ed288c42c25d0dec0156d43d010001 \\xf238349ff4d53474ce2da1237e7ba52cf4f4117e8578a02892d1d2d88422662d4b60852c257f82528542f19786b7c6f8793a0021b695baef741a73026c798c0f 1656501941000000 1657106741000000 1720178741000000 1814786741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-159 \\x8ae1649ef2e1c6aed2360556f25d5ed6c75bfcce20346edb2d014e15bb317979b505d00cdfce9f37fef311c8c943a2272791e77f03f26fecc2d562f55feac6a8 1 0 \\x000000010000000000800003d24ed96938cbb0d832d4a6ad82ce183b3dffe3fff1ab171a3a234d067196f5b0e20b3630760095b89af1bc240b2b5b2c7d8b55c7d248e19e65a4ed94141a2ed8c0736cb28a236de1002cbbb75b1c0881f7400d2bba7d4f4d7c188a5a23e14f78802607eff93a14eb90fc2e1d6f4d28009e1b85928b5cc250acca1a66e46d99b9010001 \\x9cf1b8861ddc7dc4d719baeed2ea450e4155796f6f637a3e4df88690868d635b4b96c6d14430336f611cf9687986ac380e3f63f53ed58e2344cc4a53bcf0c608 1654688441000000 1655293241000000 1718365241000000 1812973241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-160 \\x8cf1b83a61253efa9ec60e7d3bb3ca2abcb881ecf8af1e8c3e3c6ce2d165079e10339790a298133301c807876fd05d307ec84ae19a14cd0195ef5f795c69fe3d 1 0 \\x000000010000000000800003dcaac64afbe06e15e8b8ea81803747d6f584233c50166814e61f783a819a319e598fc06a41237e5775a8104472e3ca82c74b65dade81f6d743da4585ad84d98b2e5b9ee47d3461b45c8008645f0da197e082a2f13283e6b87a31ea1dba2098d77b25727c719cfb604fd771498a62ae4254a8bed467172eae10e722547f4fd8db010001 \\x6a2d8d0c53d4b6a46d6abd2c7d6f1315ea04f28db5777dff410110985372c09e97aaf891457bcacb3eb5cf5f578eed883356aac784e569d8f4d16113b3d69905 1658315441000000 1658920241000000 1721992241000000 1816600241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-161 \\x8c493fbc4fab2803a8fa699087e495418926ed8aed5d9bda9ab4154521b1321a70a975cc3d89d0421249535bc222f6f702b3ce77dc9966fbeb7b069d0703af13 1 0 \\x000000010000000000800003a5aeca5ca85af62b3db444077d6579249db9a7cc887f6537612b7e662276a51e2120fb5536a1849a4077cf0cd9088d0dca3c044bb715df76f2c39078d85ee55d2c9c01f805b0567771b0229d5594b0c5cc10128376345dbfcd73a024fc5e2fef664af6681b20593c77765f3df4a106e5adc060beda1ac2a1edd2a9e828392a6b010001 \\xfc9cfb5e344cccb19cd851f8b2398cd5d05033d9ab8ea8a3e0c5ee752534deec9e3853572f2459bcf53fc1f80a27ed1c82fb481f3c0c53ea6ed563711c0f040e 1644411941000000 1645016741000000 1708088741000000 1802696741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-162 \\x8ea1b233073595837de69d526abe170e11b11abde4e881f4e1cf82698d0ec2eefe3e03ae130de1de5b4e866b159e59ddeba30c5ab5303d1beb70a40e8dda492b 1 0 \\x000000010000000000800003a13ea68bb44770062090462d5c0157ff6ddb98fcd5ec14c9957dd8f84c3a1d70c2d10d4316bfb5daf6547a3a740e714373fa52b2565118704662d1b3aaa68f4ffeaae0b1db36d58844cccbc6609e59e7ba573d9532a89227b24508d588586045a4b21ac9b968bccfe80dc90e37ff578f7c7f396eea45ffb9cfbc3db2ac8c9c5d010001 \\xdd53603eba287bf4b09bd6606474974faad2777f8a76d47ece2535fc6cc960e6905c7844bc72f62664a88961737d885a0284b7fa6198157e432d12870642a707 1655897441000000 1656502241000000 1719574241000000 1814182241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-163 \\x9719aae38cd9493a3868e4bb7d3b3f579297ce5c206703e0571ee695ddbdd073826d851983aa899ca39dc68929ffea677e8ac5af19ac91d6b9e25ced2992cef2 1 0 \\x000000010000000000800003df609d15a6835e8b967a59aa268f7a8e1ca314d7d58d834838e0f0be3099c53b4374624840b75ac8de329d7516e2783af68fb77026153787023cd714dc57ebd1a4d9f48ab6d3fbce31c7a56a4ba40a70cf899297783ec99403be9dff790f00392a650e93827e32374b8244dc648c27ec211d25f08c883615e283275d7f1c60b1010001 \\x95e96860c5de90fa02b58c58879db507e87c93737d3264add8129da0cf1c54a3d000c08fbcbdcc7274bedec474a50f32658c982fc204da12bf13636fae8d6204 1653479441000000 1654084241000000 1717156241000000 1811764241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-164 \\x998dabe0a3dc4e620d5a7923cc59a5e88e9f8becee5436c7a8ecd105e44ab97e4c75813096f6fd77be38df48a34be87f2b1f167599a7a1201b448416066f9e77 1 0 \\x000000010000000000800003eb4a3f69b6b05af8abceadd9bd25c78e3c7bed6821e9f021c3b352c574678261e633a4f0d939af419e407caa2ef59c1b6fd171b7543e8c209a5eb82d57a6a6e78d253113006ca6f15241b3992b222ff67c21c7e0d9b6d1c0dca4643f6ed31deacf89d7e23b1bc2d100fc040b318e20a90a7859f17a6c87389ba0fa0a16e3cf61010001 \\xad86908c7a2740646bcb437ae6c598a7afba62add9a529a678d35d640f5f72c5ffafda2d610e86b8e70847702e04699da7dae92ee01aed7c12ef9e67c5cad70d 1669196441000000 1669801241000000 1732873241000000 1827481241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-165 \\x9f41f27e915716767c7877bfdbe8edf1e29edccbdc7174375c011b0629b852b1e43edab3c966270ae447c692f453218f9956b897faca90c6e077f691438c2c1a 1 0 \\x000000010000000000800003b82ab7ca33fd32b7601eaed0ba6e8d9973de495257320e2d54541ad77ce0e84c4c6ca6c99fc81fdc48827612bbd4ae48a66e4a722e3dfe65b0008fcf9bbdeff074fbd6a2b2cdcc18c300e8a8c50e88f99842f4bd4235741e3d8a192c49baac8afe49be205fd9c902ca50fd5a462c642ef8265d8dec0b643bf0c5bc40412dcacf010001 \\x6e09f97e32a0fabf214e7893b3498f430804d1c889c533bfc047843ade91645cc43861a13eba4566a64b6b881d8d0fc457f037196090c82b2349b770dd995602 1638366941000000 1638971741000000 1702043741000000 1796651741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-166 \\xa1359742da938611c1fb6f81bd19b0a07bd7b070ebbe5dd465df1c960027bf5719c3eb65332ec7e06a890370f3056a3b0b2fa8789a6af82cf49ca20214b1cb3b 1 0 \\x000000010000000000800003c62c80c18d65bfb39f5b90cfa25fa0f41f531475becd1c994d92d542468a8ee4404acae1bb9cb1bfe3d4339d26f88f786ca0d465d06661dac2cdc81c4c0c488a3592b8fb9a62e08ff791cfbaa91438c092d0f14628886a56891cf5ce2ff7a4f4cb43b6a438e5822246b588300223ae1f5b97b7fd388468c078393ab9598c21eb010001 \\xae7e15a0116333e70add9d98e633909e8d1fbfc35df28f33ca398de98cac5f60552e719ee29f741414b795163af2770db1cf1a8d18b2be778b2f3d987354c602 1646225441000000 1646830241000000 1709902241000000 1804510241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-167 \\xa241d642704a27105e8220bdb4ea66127b0065f489f85815b75cd5f5257492092f5451f28eabefc16eae761737b85addc9b9069074d4cba457a892c1335c0f8f 1 0 \\x000000010000000000800003ca1dac06c9310538489c1d7a92f63fa7f089f7882964a47156548bde120616bd935d63a54c01b0c44b70985ed819ee2ba735250da96c6aaddfe76aa1f34ace26d5d10df5b1726f9eb06dd56e0d9a984f9942300a1225476a4801f9f7b50f73b7b6ba9c86d202c0b111a0415d280e47361cd4bd15a16b4e58df0c7585e8c930c7010001 \\xc20bee99434a3ff9b9e249a6969c21ce09ad52f26f366c0cfc4fdb98760717433fcae23f23c6949fe5d2e16de058b9bc48574a529e48c79d34e562cf1993910c 1640784941000000 1641389741000000 1704461741000000 1799069741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-168 \\xa321c2406139244f036a179d1af41983fc34eeb919cf7562b2ce7a86d27593aade336c14876caf4a143fe1185cd8359728665af2af1f07a752077f0cbb94ed57 1 0 \\x000000010000000000800003c579bd17b20edacc7ff7e65327c92dedd095cb5a7418e750f2aece797d22d20e2c0ce95b0670ac9605a6d9a4ce3d9fecd73ab2a0a6e7e3b96048857ea4b1195e7fbeccd7011b865e584ae534be2f71b274ea383e57f128ddbd37045dc75e6b44e44789f941b87b358954656615bb04fc1d85d99b4739a630310261ab6e29dabb010001 \\x083a87b2b1ff2a11b32cc056b51befe697453f09f00dd7eec6bfc9a166f567f5e34625f5bcc59329fe5e8bd6782099cda37e9df58103705a33310045c826230d 1641993941000000 1642598741000000 1705670741000000 1800278741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-169 \\xa4659d4301e709affdf1503efd2d4bdc31a7e4992ba323b6902496f419acb03cbba31b33bb4c1b11784c999894145cc277551ac8a6e98cabb854c30544ff60ed 1 0 \\x000000010000000000800003c4d074ba077bf2e34cf4e6657eb9300022b55ca53b22eca529dbcd416ce088e451ca4c5f743043ef99f4160e11fe1fedef250e3d4762e3200809d6ab73df423cc6de39d07b37a1c82f3160706596d482defc0533eab8333d597a1372e707ff4bf60216839ab0bfa65d41a247460db6b24db5cee489855f8e55b5a9e5da5b6d1d010001 \\xb9e1d238a773cee654ebffdce9cf21bf676925f3b3bc3d00fca56766820244016c854692c721efc1c51c1609d7eb38f4e584b4bf18d732a6d6bbf0e4b6b9810f 1660733441000000 1661338241000000 1724410241000000 1819018241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-170 \\xa4c9aa9fe2a3ee09ecc5eac4890c2a6cf54a31908aaf2e4c0d3c72802ef5bb9b02921d0bf25faa64f6c7f1573bce7492de2b9233b3b70426cfc53282e7d7bde9 1 0 \\x000000010000000000800003bc2c7e86c86884d1c6f1c939d9cc6159dd7db7f956ab759d21b4888a5cd7967da56bf09a8c38a4def1579028f96a15e29f70e96c038162971d7384506e240ab10e0889e1f62f07934a93e08fe3792d150d77b24d2385554d1e9845ac62b3d9fe4b756d66fb99637da1a9732be13c8486401a593e1a266d06e0534f4bbed0cff7010001 \\x87b6cf6a182a6d01b3ae68bcb55e20063aad203e459cfd12494c482aafad027349c4ae587c887689f2f3dbb03b7ec4e6b983bba1fd8661cc3f8642f53c4ca30a 1648038941000000 1648643741000000 1711715741000000 1806323741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-171 \\xa59540a8f2706845b9ba3fb3dbefcdd780829accdce8958917301ba39f4e85fc21c9a31d844e33319fe35c52d697958d1a832618d72576f106076b07f9025454 1 0 \\x000000010000000000800003dc83bb477d0c2ff0385aa4b4675f3e9c44b6e1d646b9ebebf575a210f48426c5a94f15b4373c0cb66f39c00d30c50f839977a5da82b08454bc917f45a1ac9cd8fbdf1ec245fee9ef9ae675ebfaf028370cbf5a0a0b004087163805cd5f4c9902d562eb4c50e027e659eeb2240117a1c0f603628769d6a21d904e890b501038a9010001 \\x5a0c23ed52498fec55246ce96bbfb524824cbe51d7ced73d31c41316ce6f0de80278b8cec78257da74d63997a24e0cd2c2d4147fcbd4e1409d77b5c420a8090e 1652270441000000 1652875241000000 1715947241000000 1810555241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-172 \\xa7c940fadb5f93a43452f548633ffc3bb68af3d6bbbdcdf2f47510c8695bb702b22f454cfd51c65ca71ec5da493aa730f77629e03a9247b05d7d2fa2078df222 1 0 \\x000000010000000000800003b0ec37fb3963a8770aced84839e1a2be34d70245cde0998df9e7d65732a112785b4aa86846b730981fe05d5c63fa943587814349f2bc6169775881dd5dfdefbf4218af29e5de332ac9c62555e35c431cf9eafc10fef2b99b729e94f4f3c5bf97c901c5f4cf7dd81f80435c9080b93754725f407f95eec9c4139a2a8235225bed010001 \\x742492c6ae8b66b3bdb32276b58ca14bc1e65eab9c0deb27d77c80fbefeb96b9a94bebac852e500deaa01ae4d91b3fa7a017c0bf446d2ccc9c3a50b24575fa02 1645620941000000 1646225741000000 1709297741000000 1803905741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-173 \\xa805f846a0780315b85d4ab3f417b3f7f4f7f5af42c016cbfef0f75f5585dd24f6781bee7cb26c3390a441fec85c6ccbe32d61dbc598d6438639bd7ad83f6a93 1 0 \\x000000010000000000800003a5fa5f3eeccb0f487c2a9958cd221a0420fed2fa4fd159af3ed08d646fd4ba84da3a93e3483395683531ef326b700e26ebef05835e33421200cebadd70c6a04c74440a93933795b042efc4abc889d7fd887c696f362acf8e0ee9e77720b7f7809714971d600599b9c54450cb09157abd47bd49ffbd2269d9a33668ffaea70221010001 \\xb80468e57775ccd4647e1dc4984aa313306927190591aa642e2c0c95d65e888b794f826e7bbac8354b6a75f519e77c25ff213f330ebf4364b885d6a948e93107 1659524441000000 1660129241000000 1723201241000000 1817809241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-174 \\xaa39f5e3da0c5965945ba77576d2a28db91ced68349ad3ecc12dae24c2b120bdf4628793a34958749b13d055c970ec93a1fcc9135fa5ab0c6ee4821981437286 1 0 \\x000000010000000000800003d9784e8fa795abf1a7e17490614612a1c65e54c55eb58c1e3abed72a2d05f262f688616b1e8e4d0a2da8198787f3619084fd1962a515719b0b9bfdd947eb7415e315c40a52a7de13630a6befa5dc3a780f24b2f96b5716464f3fd39b88904c9ac4422e621721256ed1ccaeb797bcc0e98ef8ac9396fd6bd421ce3c0a2131223f010001 \\x22766233025a6c8122b882ec8eadd5431f27115f73bfb012c0e0c639efdbfd5a04aec74284cfb55043e23229ea4178c161ed996034d236cda7e2fb5363e24301 1651665941000000 1652270741000000 1715342741000000 1809950741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-175 \\xabd5ea71b7fdbc11886e1262c19347cd769d6d1c34404a7e1319334cd3e8b2a77d7a1afdaf807252bb1281646ba096e89537ef0a2732eba2a14b4ae955e83523 1 0 \\x000000010000000000800003d14b457821f587c81a058638bfd648f28b3600dbb44669612650d709e48ae6636e51247ece784b8fd1b49d7b016e28d0316796113a2dae950635c0a306f696e61bc96ad77e25217ba3ef19fdc7038b716978ea5d56f27a298c7a0753e0d3766c04ab39025c697157983b6cef35a44769cba1277068459383044024cf15275e1b010001 \\x6f15a4a65637db92b283ccb09cea30318388d11099b5bf1916b3668d019ed9eabdba43c9607d958f719f4257be70d522e5adba0bd701f5c3dadd81344a016e0c 1657710941000000 1658315741000000 1721387741000000 1815995741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-176 \\xad49f2276e63b0d39089768fa370445ee1c0cae55a75098fc1f38cfb2e6d754aa4cf2d587995086cf00318a65087a8b292f3e50a46a81bd6437752078e702006 1 0 \\x000000010000000000800003a808267ebd5c66b26ef94a47140252548f28e1926f4717abde12a55aff6542ffffd5341fe6e50a90ad991097e3ed66300d5735990ea8e33365bd656606fa6869059f390b108caf1f0400a0b93be298a5fa8ce40cd9863336445172c9dcb634083df10a6c716d9811513e31616d86cd5a60ad6229b0d3b43e177386edd9c255a7010001 \\xfda8e319a2bea707894ee8f2fc46d99bc7138d3f9344e4cae49861991cfbc52ec5a42cac594bb6f5d700a5c2a58f851e0c2adea6d3a451ef10731beac0df4d04 1661942441000000 1662547241000000 1725619241000000 1820227241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-177 \\xad6d586fa84c847668f0c86fa6a1f92af7989cfd1708efdb69eae4c06a6f0b61ab6d556a2827871daaaddc96e9243b7abd2b2e13862735743115b283d6a854a4 1 0 \\x000000010000000000800003bafaad2d71728687f1d7aee49250ad4a2fdca2b7de2f32a1cadaaa65ab8c9b45a3f9179067f10ef803e54976df39f6efe5190fd9ef6df22905834bc686f76745f3eb0d4886d1395f54f50db736da9647ba7dbf9e60b28d05e0ade78882e36fec2bccc9f4f4b329fd9b4a16020d10246439fdbb0f04b6290e5a51eb0af3fad08b010001 \\xe80468664417364af1232fbeb2bd10e5096b52c4b04a635a61d4198d3d821c56bd36d7d1955fa81fcff948b96b8a209145228fab61cae6991b0a7bd5d430db06 1648038941000000 1648643741000000 1711715741000000 1806323741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-178 \\xadb9ff3b62462833d51f30459ea16e5f6ef3d2647ba1790248acbb0c0fc8dc458f863b579df7c7e1a8e8167e11ee28deb93ed9f8154c7f0cb78e2834595ec038 1 0 \\x000000010000000000800003e685af947c8cadc6719d66e6e9c01142823b7ebdbe974ab4fbedc3063fe1a202cea2f6e1648216854e44633e6b454f82214d85edac4e5cb33383d0138ad50684335f646f812167de8130d4e190b705fbf9dd376a6db57916f65716e14c4591a516c249d838d3edd72e2764e30de0488d4b5ed7d19297c2fbbbe9e96a1018e2bf010001 \\x094d4ede388391e4304515d99edd302fbed5c3c391aa1476a1fb64c3a657117f385dd4419f1d6f841ce61c72a44fccdf4b6ad0cbd0c85b2636f60fdf3a9f6a0f 1653479441000000 1654084241000000 1717156241000000 1811764241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-179 \\xb1e568dea56525947db0fc3021cd98de1e3af66715c1105dec8141676ba8dba7413e9e7321a663b53ec621497a9fb438253557c1b6578f4bd07f2d4b8d150d38 1 0 \\x000000010000000000800003c3131d2f5df8c964bc7a20b6fb1f331ba0fd22e868cad4a0f58241fed57f6b69d3503c403cb7eba376276823707bbc262e7cc90b10cc15163fd07c8fa7035dea81e7399be10bcf2ed53a554e47652ea05f3b497c9685552515285501e8f86fb7880ab7ac9571393b1d3d0e9e55eba6090a3c89ee02d9dc05951fbff6da8f3a21010001 \\xca039905ff1ccfccd0f7cf65b1e39d8be677b25e5064b89b4a92ce89283d50ddcfd5b4aaf136a8485ac6d137fbf1603f5c3372f3b40770e1cb828987dbba440c 1666173941000000 1666778741000000 1729850741000000 1824458741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-180 \\xb7d173e3754331fb7bb011b3efcdd33f0cd82a5e85b849033f3dca27607bf4d79e080bb36d29066592b0927a25b3cdcf7864714fc38de6a6402ac59c48f7ee27 1 0 \\x000000010000000000800003c31a779d0fc8a596b4d81f38957b3bcdde08d5426dd1fea61695f9cf4961ae5780e1df4598828e38f42ef5545028bb855342d8a44c592d42e35f1de1583e1c7d003fb2c8503c89d5aa59acd9710244895848a0479031f83ae2ab6ce690a251873d979ac390628795cc1e132491d898ef2ada5f10463f726017e8375a02f1885b010001 \\x3bfb79b1f28ab7cc728cb78ea83693ea36d9ae2a03e6b718b2ebd2e32edd91779c420f203b49dfe30356f7e9c61b631fefd15a31f1497418f12ee4f9f539060a 1661337941000000 1661942741000000 1725014741000000 1819622741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-181 \\xb9ad89ba989416d95f0471653f284d1681f297cd02cfc53040dfb7ae4e8bb9748a107dec7eb4fd1d45891ffa64c2ebe06d9666e4110f8c70deb81b2773181dff 1 0 \\x000000010000000000800003b027a5aa66bad69c78d933c94160a81db7496e8cf9a73d8adf7932b0d1b6b4f67961c897b9ed82b2107c213cface1959eae5a8ea33c1a9bd6af2823c6d542b6636b5cd2c7612143ba176ebbea8735a03b8cabb3566d234469111f8dc79d537a2298f8b1d7e486bfff2e72cb7df08c52ef27fa585c7945f144743ee1d428a9363010001 \\xa0c2603ed405d017e2ce303dfe3835eb3db58549aa65e107055f878a4a650ac599a88b76e9aeb03df245d6ae694954198105057002da979f1fea367bca28e90f 1668591941000000 1669196741000000 1732268741000000 1826876741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-182 \\xbcd534cffd89aebb7e761a2ca9cb3e4f1fd3b3cb3ab3d8d7e665ee1b0f5be08b2479c8c14012e5c6b9e6c795ddad1d9a6de7d8ddd45cd2f1f2f9fc194b8edb62 1 0 \\x000000010000000000800003abef5e01b74c6b74569367f6b569569df63fd87f5f5978f82c6c14607128e673a3dc51b0ffc3dd1eefcaa79ca019fb0f5054507d96fd0555f880d2441f43997c16c8532b6d25357fefd2af101fb2a0e597ac6a845d37ccb2847067cff6ace428371af004289e71cc2983820ac53f4cef94d1c316bcc61f8ce1c6ad788042e213010001 \\xc957ad7321805e6fdb2a07d1928c1eed6669a979b7819450103fee8ff93d8f4722ceb3f5d1cd806aad8353236e5f4ee2926126c4a190e7407b3d3a6dca71d504 1638971441000000 1639576241000000 1702648241000000 1797256241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-183 \\xbce968716ebd68255df00e1843cbd079de16b60c4b4f3e8feee6076e64cadca11b468b3e3570fef91825c7cacf31f6bef5841b67936d793c7a4e776f431b6e1f 1 0 \\x000000010000000000800003be838ac9eacd446a0b7cb3928e0f595020d726c6ace5273f6cba997e7e74b26f11298cf99ddf90aedf49c7b2faef03f63890ab2c6aa91bcde7a070b5c04a41d74df2830cc7ac6d2d8c4d44e3be70c161a225e7309339f9ec1b57cf6e72a01c0c24c885f0c465aaa4930112dd9c3925fd084ab65952b045d615102b1c2e557557010001 \\xab55183d3eb399f6ded2afdaa4541f31b0505a0552125dac08aeb5981410cdb6970a9af250e5e8842385a39fbed89f427c8af99929be2fccda3e9391ccfe4902 1662546941000000 1663151741000000 1726223741000000 1820831741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-184 \\xbe9db2fd3667cbba7af9d0fc6f19ef38258aa2e59e92ffb1ba97f15f8a224484d3d9a73462f72e8df4653abfb21f1e1e400656fd10b413662022c9c28313f97a 1 0 \\x000000010000000000800003d5da846f8639158318a07c6e2ba9f172f80f9a4647826d5850841fe0fa2f4b34382c2a0966e1031a686b1c9e9c6a4096c91195a620b0b5f5122523096f11382ff65a86011b9062918ccf1cf65676dfe6e3f15aa0ad2567971f99fdd0eef955ba41262bc16d612e3f85ee0c55074e983404d4e59368f21b6e912623f42b8a22af010001 \\x1e09915425d9a4182bb77a4e3480e00ad04b5e64f17c39b6bbf067c36dcd99fcc9abb230c08d4baaec23bd32c9d1541e4594fb0d3268f6eba284934ddda74c04 1664360441000000 1664965241000000 1728037241000000 1822645241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-185 \\xc169f9af937e227c23b68adf478b2d30d08f30e64d856af8650304667a268eb78dc576179fc0bf622c6db4ac535cc108616143e866f35bbbdec5fe49e4feb18c 1 0 \\x000000010000000000800003bbc12154c30a3dd41e40a07779c94721f1d323deb9b2b6d8b375d939cea920b14d1e7760736dda82fbd958d320360e9e46e133edd78e3fbb30069b9370817cb8d5680b446772ca3f84860f3dc19ac569acf3588c3f2cb8683e96aee05c54af39e0250a3c9b890c4af4a55553b1e632bdfc239ba267965ebb612e9874ae9a54a3010001 \\x5f76b883ebc1e5afe9a66d03f2c0955f9d99a4477a98d5c3724501a5a56cff8cdf74b3ed62aab74523f00a6208372864fb972ac018d5c7c6bf3e1423640c1700 1656501941000000 1657106741000000 1720178741000000 1814786741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-186 \\xc5f16e7da021c123178bf3a9d07a50fc50dd2f387f07347452e9326fafe51355570634beb78cac16186ee6e2d2310dcb08c045e1b549f9e3cd1d4da80364bf6a 1 0 \\x000000010000000000800003e38102ff9ccf9e509646ac15d665178e814d81a6b8ff0891c15653c21357ac78b1f51e8bdf25798dc96ec050360695ba0c099f58ae0e4b56b02a81cdc373d3366d805314274d79c5b1f4f268df404e84422075057158068984a0ec5573faa141d3b2df009db836ca58e1a90f0e50615294e984fa7b299eea66f4b47aff513fef010001 \\x197824253a9f0e8ac82e12b59c931ba4a3f5794e937acc5d68515bb3cf0785afc92315c62e2087ff137f8a65d160f8f9a98f34afdb804fd74d431017826e960b 1654688441000000 1655293241000000 1718365241000000 1812973241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-187 \\xc6a1f1e89e5a40e971d21c425d4e38d5dfb9ba10d9a17d3dfa0ba1f862db9ea1e431ab7a45ede08313d0c9ca96bb7c180e575a37888c79d8f18419d5b2ed2fa0 1 0 \\x000000010000000000800003b93058c48094352fd2f8e0407ff723cd3a65696afc96726c981e694b4d496fb1d8d78ab0ac393bfeabcc26d6d6e2b1b63a76e1c5a95c991694ea7fd4a9cc9b3f72754223350fa1d518e3c41ae0900774920602667750efedd02ef4171c0a811dcf1e9865017caaa3f5bd4459eb3f55a63fea2c24c80194ca474dbbf6edad329f010001 \\x7b977fa0a96e9c2f63d9241c1ea0b9d29156f922020fa7d917e663571ca924824784bc93cdcf24a900ea59e2d74aa6a7d0bd0c2c3ef42ffd0483245cf5ff3201 1641993941000000 1642598741000000 1705670741000000 1800278741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-188 \\xc915d9daac15cd610b442c89e14ad9598b6d375077d25d029a2c330ba332b71a4519cbb8e14a786606114ce57a09c3bff30dac358024c64419a0bdb8222a8b2d 1 0 \\x000000010000000000800003bde5b45286cc380640ce37caee258c5af07a2a29262b483c86bf306d010074e9dcf0ae9e23a2ef0ee8bc5f33d3b0c9403566335b5db2e7b460213ca7f3169b0806af809f0cd557e9ace86ebf5449f9f8f21eb092be6bd7bfc6cb73c915d74650f903789e287dfc8c21824185d014bf68eb0e00c4d9baf117fcc45f492048ce15010001 \\xf57617d5111240f2ce298f7ff3678943994c8da18faea21adf4621c57a17d98424518ad48196f07a847ddab0e36d0578029d59539d50f463ca9f9552f4584a0c 1654083941000000 1654688741000000 1717760741000000 1812368741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-189 \\xcaf573d1841a79fd8b8d805475b537b6c5f9408dd3b228ad077add2745348913d0f2c4a16e21da1c0f25fa4b4b78a32e940908d6c6b7c9e062b746aeef32318a 1 0 \\x000000010000000000800003b141ae0c9a5f94d4dc76ff74d8315f47eb231e225754d3829a455f092e05de96213c94f7f731b5cfbb95a79daaefe489d2f2b4d9b7c1a6f2095a1c82c21cc2a813321dcc3cd3fbf93cd5f7664627fe4e6ab7fc8066d41dadc16b7d94253998a0a00ec5f0c81e2e035256f264397bb6c5be1e37612fe72bc5acbc876e8f8f627b010001 \\x201260fbd10816a15533182b61e5a2647a98dd19ca7d4de718b012be945724343713af03e97e2c7da5cfd6dee52aff4ef40dedeabdf1832f111d00cc86f18a05 1658919941000000 1659524741000000 1722596741000000 1817204741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-190 \\xccfd6545fe4f00c677892891096b8274f652ad30f86e77c8641da8628cc00c98f59ca2be63963436e221ee8814ec1d684b103f67a2156db2d311cfb8ba789f2f 1 0 \\x000000010000000000800003fc9ad5a124c6b69477d5e8ac85d06f28fe065cc465e79edeee7b1daa28b7b60685051d05e63bc7acd25f7c3de981e99dcde476539d5d1a74ebb34a0c02b00b388a3f3765c2b89bd7480e178ffe9ea086a5fe77d5ec034efc50baf17a78521d3fef508bd1e2353e359a4ceba63bcc57fc82cec652b5a3368e2edf93d4c59d4521010001 \\x14a42d6377acae4689c26d6e5ae2a3c7e3ff8a134bb3aaa34ae3602ed435f6006b1af733f9ecf72fab11b42e87a278fecc96b6e28e3424fff4c609ff18795c04 1646829941000000 1647434741000000 1710506741000000 1805114741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-191 \\xcef98cbb22efcc9b5b63b2ec640d1a85c77de7a0f9e536583fa2c77d9b7721f7c48ef429513165f849be4046e9b98513f40e44495ede0a0e4a312018db8b5de4 1 0 \\x000000010000000000800003d74b7870160a01e5a4b79e009c75d7c67e96bd7446a0a467db49d50cb527ba4695274d3e9fcd21cdc5ad90749a46fa0f4d1bf9de71b9e2a060c05576dc34acf4902f4794eecebaaac8376431e41b54726420146e00bcd54eeff87f345f9c57af7bedad7719f8e5434a9cf99cacaf641f7a0b3217493c4e3b897e6b49d3a4b437010001 \\xe8adccd0456d421b99570d3badbacf8ad7161f053d3a64d4f3de45244c74403e0399fe2e297fcb5386ca3e1f6c606c2d7f2eef9d42dc7bb7380c3ad5af440707 1639575941000000 1640180741000000 1703252741000000 1797860741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-192 \\xd03dd39b122e8e188fe3a4b6bef684b2cfed653112d882434ff2fb4a60157df165b89522785f24234ecc888dcb9b3f156792757efd7b646e8940de4ba21e1858 1 0 \\x000000010000000000800003bb482f25d36fb6bc0b46397aa9ea3d55161d5ab8d2bafeb9a10193b6858cb1ab606344d9119f05352bb233962bf3f08d6f6f1ab7802d25e96b69e75c87eb8a8d34b0929d3bcff81c855e79c77e029483f26ac02600d09892ae1279b6234b6f02f58e2028a339ac5e7484a77b051519652f1e4712f95c31d7043a7ccea2ae25db010001 \\x34bc60f55886cba95c2fc986a2a8ddbe9cc5c808c41c109d73004040c0bf90cde16e5b05c4726213da092e84a7bebf0820c16a93dc329f4a3a02fba2e6c4260c 1665569441000000 1666174241000000 1729246241000000 1823854241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-193 \\xd3919f6c4910a1b01bb46abb6fec5ebb30130ba9109f888e29b65a5eb7458b710923ee70adddfc9eb3faf20a732ee7d5314188f73cb15be0ebaf4023d28f9b5b 1 0 \\x000000010000000000800003a3fd30aaf5ed05622a819e580c18af3c3979f488dcf2ee3e1b8795a6d76794ca322b0e278a0aa003176c79052227dcd5ceb3c717279a810aedcc3359c76b6a4428306c9347ecf720df7b04a41fa90849d70ecb42713cc4906b4f1358d9dfbd3a2f929a52a479e6484f12b433e828ea9897246a69e90cc56f945b801f2dcea6a7010001 \\xc68eb1d524689c8f59fb8856af112a6b5cf53a6415cbe3b95c8cdd5169c08a3f19e666cf9b282ca47b66e80636ac7cb3d2c4c77855e122a7524651c28998f00a 1664964941000000 1665569741000000 1728641741000000 1823249741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-194 \\xd38dac135615339960dc90e10fadbbc8393245ce9d89a6397b04ff3b50b1f0cba3d6fa3aebc2d82626dd2d06ddcbfcce75166c2c2ee62ec193cf6b012004deb7 1 0 \\x000000010000000000800003dae9cb624205a3b9add88617d7b95f5301e01c062392c9e74f6a73f6adbe099489e0401572df4cb26c2739c245f50750a31940c801c6ec8f8ee5d9b6836beab7b1ab32ddaf72bf940f050a70504c071911130daf3f28f5a2fae9857a5add9135807a80ec40008cad3481a9bc7f90d62afbed1d1c6cbdb0b0fa041f93b4dc5651010001 \\xed6f16b9cbbcfa558073d4bd582187ecf1775ab41190e41a818c2b768107080f56b30988753739c5377ddbc9c2bf771c53dde8214e58d30e085e7956745e3608 1655897441000000 1656502241000000 1719574241000000 1814182241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-195 \\xd33179d5662a79168e554c9f37afcbe3dbf8f03a1604701ccea6a960e1a13e83f7f29a9fce44b0b3671835bbc1e89d1ec97652d79c170a5eb18279a34c3ccf0d 1 0 \\x000000010000000000800003c9e602214e29910334eab8c501b089618d74943fa7d5e82511d0e629c10596dcab756e93295760183909bf179eaf4738f85049862b3b3a6c87c38ae6bd9539f3af534f031f4625d8f01e52d12f84d30ced1b75c7098ffd1c36c9a1277be23a99846060bce79291dac215434c30a7621876a3b448b17e72fbec70fdddf1740e61010001 \\x18b4153b79267bd12ac4bb05797d3d872cb3e957c95963a917f4d05f05a40d70e3f38a99ac4c678c21d6de8b6c7fd2be2b482564eb2fb7bcd5a5b04d26cd2e02 1663755941000000 1664360741000000 1727432741000000 1822040741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-196 \\xd48db9364473dbcab4b468919eb5ed110f46737bec5c7bdd545af42ef88c9d97a24247d4a36bef5383b1e0d080bb53b05436294ef6ac4aec26fa730a6edcdf9d 1 0 \\x000000010000000000800003c8d15d7afd45dc80a6635b978063b9edce87a86f8fc14dc2d5d28987927422f53440c13f91579cf8e1b9922509f8ebb09532eea7829ddbd49072739808eef1e1c4946a14a7aa87bdfd64386bf6e48416bc8746bcbb4b13e53918df96350525934d2c47ef1a73c88545780b7ff388e3321118cb44cc28272988ec17097a22ee9f010001 \\x685850d48aeea8941ced975b43b4919755208628b467afb76a1dcc7d884092850e57573f9786322ad43a03cc41f724770d5c91c813153493507123f17970cb06 1666778441000000 1667383241000000 1730455241000000 1825063241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-197 \\xd6f50c572300fdf6becef7f259899c17e4aa47fa435902a01ee15869abb21e31d22406d754d83932ba436dfa7df41e0f83140293b5c51f3674b7d2d6fd8c5b47 1 0 \\x000000010000000000800003d1c3ab0aa91302e2f4cd47a2ce5710147018f8ab4a012a554a036f2006a0c6c88d00a9b91bfb9ef04a6adb700df4a761bc5713504f7c9d614146fcbf446e0d6b6c719e67e4686e0ec97c5d16a3996458455dd1ed2de4ddeab375422bf15db5d6fd834d8315dce0ff1e32584de09eb6e687897d8135ba17ae60f449917797b561010001 \\xc38f700e3a2a7169ff136ee1ef96d07693c53ef34ef555e33d1acb3570427f2030fbf49ff2089dd4ba5f0cadd83939af6929b9a8373c684b48ac6ed89fd0b104 1648643441000000 1649248241000000 1712320241000000 1806928241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-198 \\xd7b956d59b11a54c3809d18cce64de14a8adfc7c6ab52c66bc8f186e61ffc19079a378991917a3b4d9b0ce5184e8a947b7d97a4d96febf74f419ba662ff37b26 1 0 \\x000000010000000000800003befcc04da7e8d5f9dd6b9f97c7d90c7b0fff56cb9bbd9c39e105a59bb287fb9036be978e9dfab5c2b1e67790ea6c475e6e0236f8e5ae5a147cc0b6cd2feb732197111c8b1f48ca54e3219e9ef51656b477e69ef6510a3003c44df0fa48600bd724ecb5cbf798800115f5b5472e9d9a7ee21d7286cfd01bb88cf4794c9141b031010001 \\xec45588d290b87bad276ebd65786dc53fd27e7330bac31a3dd0ac77f14ca2c87a38a00dcd9311383be3d8d6fd6873d39042a404cfdfe1ad95a1ab07398482c0f 1651061441000000 1651666241000000 1714738241000000 1809346241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-199 \\xd8651dfe18c7e22d450f56ac302c5717565350a360a1bcf671737924143148e67e7c231c0e0a68cdf2d59dace92defff14af9bd0109688c4d4f8e7a9e974afb0 1 0 \\x000000010000000000800003bc1b7af188742efb845ebd18e29a53c52d793e31126e470ef72c7a723e363a5ace37f1e8b443a8fc8eec50530a5a7923f26fecfabafef02420a92a45c60e3086c5008d4434d3a97f7a9a2ba2b7cc39f36df4c26ae183c656bf17154f49acd7abc7b4669e1125edcad45216a67fdc9a4eda4e295c1919827eddf871185b05ff1f010001 \\x34131ec703f9d4796e2874f22d09e1b6cab59692edeaa37c99640d048da3f0115d927f108917e1e2428a6026f34a8eb643fd2f48204f3edbee0325246162560c 1640784941000000 1641389741000000 1704461741000000 1799069741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-200 \\xdad5f2d3551ab0bd3ec098e6afa65b3291e8a8696a61b452cab3d79bbdf4252bc58b59a5e59cd26f225c65a9bed5f163f13ec44b61fb86c703ac319e1a387454 1 0 \\x000000010000000000800003d48c2ae3e84e3ebd82ffb54c6b1b8dc25864b7901e67fd514a6fa2d5c34f2d9f136cd988083ddb2c5002a74dcd42b38f94d1442eb16acefdec760c19c5a90c33198a6fac2837672ddc38974cc03a02d90ee8a566e921fb8445732dab47a34e30207d1202658f41a711ae2b90b38d49b0c84dae8a6471de71f4d9db2fb875fe91010001 \\x888cc0706e34fbdb103fe7ed884ad770fe3d67e9419cc7550d7a0f1db14ff51390990a9886c1d3d5a05f72259c2c985ac0c328247dc039aa0e1bd884f1b60d0e 1659524441000000 1660129241000000 1723201241000000 1817809241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-201 \\xdea901d15d39f9d4246a9c9c91a5d8b7d34ef665716486db5523a35030f04a929f426d70d926a10b34c34bb40c4c7e545a352a92b037e0efd05f30637ce3f048 1 0 \\x000000010000000000800003adcf3fcc2518681f4d4d48707b8cb6ba43944b58da005bc0f72227a0fe0b47f8e0b04a68ca52e09134afd64117e3ee7086f274b17f6376fc8452c01343ce783a144c72b3be6324c102c4e4d62fd833b2bc94b82178bf0d75cd4fd361dd01703ac7b4e82870d937666796b9a24bdcf179cf9f4cf5c6a1a87de0a25d77afa9f257010001 \\x96bf4cb26e94c899705245db8b2f7d65035b5d815ab669fbcb6eba4d50371070b5275077cd93aa68809f39c8494c6f2038f3e8a53bf9ac26e5275d57cb56c300 1655897441000000 1656502241000000 1719574241000000 1814182241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-202 \\xdfc9a711ca9bbbe196f2976d9fbbab36e5f93e9698d4f26f9ac28fd8140045ff939297a600c19b36472fef42f78e636f49ae5a459a661f5559cc90143a4c0126 1 0 \\x000000010000000000800003af552cf43a6b7244869dc46717f9687e1a201260332cbcd780c84b25942e5074ea16c7f48c92ade98abe29a00349b44e2bfc6ec2a096e0c90ecf2e3852951a3dcfd297ce1f98b17ae5bdc5a6184ab8d736c45f4c6c74baff10171c69e20c5b8df39ee84514d27e886c3972d25079481925ad2019443077c7be0cb536ba82baef010001 \\x3b68e760821481b1bc32fc499ac33be4b0d359f23e844716d91267cb2660ee6623965a593f1c2d314237e3ed22749efd53e51c6bc3f33f24b8607c19dddfd90b 1638366941000000 1638971741000000 1702043741000000 1796651741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-203 \\xe4c9654e7865d4ab3de52f53375b6d76f840abdf66d59ff8f13f953a9f1ad5e8c3520ea71717da9e3fe4e64eab4fb4e4f6acce9e22ca5dc8ef18f4bca53af61d 1 0 \\x000000010000000000800003d3186a539b842d1d310730ab5610d213b18587d6f3d9ed17d75a7648bc00261b89b3eb9adcd42429d9e5733c905263cff870574df8a32c25609b862d946a368b4c3293be79a35996ff7722cef9650bb0e902f0c2424d563700a531bbdbcc36a27c6881731c3268c279e9f74a8fa10f2c2df995e5588e63e188b6c7f587e6bbb9010001 \\xf0020b2711b2d662a1b29c1caa4850ce466474a9754edb420c1e616ab904f3573633d90bc36086e06c70ec5f060d99bf5df178628d516f6a36352abdb0ba6003 1656501941000000 1657106741000000 1720178741000000 1814786741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-204 \\xe569522e5685f89e62772d0f1c54a2ed6ce165f876df9d21d66ff465aec5f099b5dd9ee9a8cf059b581be05e90e38fa4a1ef01e0789ec95f69e780edcb880be6 1 0 \\x000000010000000000800003b427efdaf07b60cfd73aa833fa37f7ce1280d0ccbe34c64098ef26a881447473d4fd5ca99a27b84c5726e089272906260be523e3aaa57fd798f1088c3dd9edc922a49df32b045b9e60264a9fe610d69e14025b2dd1dd20e018130dc0a3d1c65956bf0f3a283c73ff322f6072828209b6453525a20072618f5cc7b5c5e0b21dcf010001 \\x600654ee87b01a7cbe23c2b71830f63b96ca3f11f26ffef6465115c333751246c394510c62b3cf8f915dc9fed4df02bea76997463e25698a8b1e0b84cbcbcc0f 1649247941000000 1649852741000000 1712924741000000 1807532741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-205 \\xe65156f78402b33163b33591de227e8213cf8173eb71299b5d050e6a09a649d136bca2a3df13312dd2c62a7a435b3b0728feb7f29b86ebdfa2fb2ca6d99abb5f 1 0 \\x000000010000000000800003df2a03181423a6ec9740faea20633d6e2ab252635bea35e6de6eb683c7f25e6e6dd1139ac828e4a4abb78a4388785d766b0d8d0f97c679b8cb9ae42c1bfad6cd6c8647280fe4390778f0457f3e305163013bf90a6e012c83526e6c6d6ca11d2e3cd7e3d03b946112839a5abbadeca6bbf0e516fab24badd8f7423ad2b78ac0f9010001 \\x2970de1ee2efde742b10ce49562044a98c8d01ece381342c2acff98f8c1c455f502dedf566dfe7749a67407a50a44cdaf55f8128f2ce8bbc732eaafe4fee9c0c 1644411941000000 1645016741000000 1708088741000000 1802696741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-206 \\xe68572118e1520476bd6ab3fae731ed304d80b2c6176a5ec0234009a3d4f5d1f3aea80fd2103f0f132be95f417e38335f9bca67d3664b12d6e04e3683e631cce 1 0 \\x000000010000000000800003ade0af820194a77ddb4b081b3149818d64f5905af52ffdc6c7348a7856c1c41071419881adb4bdae940e3ac84ff231b983fb980368ea7bea517b7e5d8bba5f016a393c33df0af790a2e13a7db4d5f6b432d79db6a2adda0f72681465625f57cec658bde88e8a28a7c8de22932c07ecbf411d09b8ff28e0d1192ce8045dae9e1f010001 \\x890e420a72db274d47e23bc3fc52409eea7b5543c98ffb925f0a63f0eda4dcf1c53c3fac4c2c672cc984d8129138dc43627fef9a71a410678def7f08df921f05 1659524441000000 1660129241000000 1723201241000000 1817809241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-207 \\xe9b50adabf7703a0f8572cf452f4a59249cc7d3e6699c07c1f81bed902de1092198f4606e06d7a12325c22724ca8cf850519c675019f0a9fd9afa0fa1e031a37 1 0 \\x000000010000000000800003bd140040bb4b575d0ebb47ad28a565c1216dd10cb7267b747df67297b6cb7297871c8295d4909a6f4728273f952d66d84d99a34770dd9f78a886d072e1c8fc6bbbdece0cfc888fcf06bbeca678fd390e3be193ed02c18aa4ad44f9babfef83cc3f7aedde6c052d72487a4fdd569c8f243c6c760de4d99edc8f1d395112fcf36f010001 \\xe87ee15c2c4bc277b779282729301eb29467a7f45321d8cf1a6a4fdd92154e7f6cc93b363163014b806004258e882eb74f43887ae9222de31f094820ba81d202 1648038941000000 1648643741000000 1711715741000000 1806323741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-208 \\xed4d1689489ad038bb88739e049556d1deea78da3ba01bb3f3b3f6dcfb289aaf1abe398a98ba46efba939e1cdf7a2fef34acd4d7b030ea355345516115ad7e8c 1 0 \\x000000010000000000800003c04feb3a691e9c76cd57c451238c9c3793d416465d91ca6b42fe704d6783ac57fbee46e3eb9eaa807bb8b8b46a56d8e48d157b10e6bea46908baf0b31acfaf27d7ce8d7060135802d6656f50434eaa27ca8cecc8fdd3c04b4ddf0ac5ebf4e9fe7d47e53955d2fa84ead2cc58a53d5849b47d13700c38c055397da51236e89ab9010001 \\xa83bae6bf15976fad79f2411b6c16e7e319cb1ea6b08e99f6e9cca16dffc57909637d4da0f765d4e8d9d6a9b29ff8bc0aab72803aee9c8d661db9be8cbeba90c 1657106441000000 1657711241000000 1720783241000000 1815391241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-209 \\xf631fa08762010c85f6b36d06ef9658294c2730ff5f6021343f7e617138702d035ff758e5a67f462e1be4cc0204ab5ddf07302c5d18ea8feb0533bda62a60328 1 0 \\x000000010000000000800003ba07d2e39a2d0d1f1ec9bb7843762577ceb73b119fab0b49e8ab800ded73d1e0b06cb9dfbfdedcdbd00a1a2ba8258968f595a9a74811ea2a31d8c98885fa285d01e7e257c6ff495f20034b7b26f203d80de9e4b15a968716f3aaa980c97f4af11c88e0986f9fd05bfeab92238fe39a58ea3f947d5075e5e8a43fbaa96c7c4667010001 \\x790bcd48d8385bb165a8ca684e26851f07b7311b7fe4ce89c31bdb0365199186ebb285246c1ac982fce061a477dbd6b3e5a059ce6984995c364269d602158507 1654083941000000 1654688741000000 1717760741000000 1812368741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-210 \\xf73d7632861f1161b5077f96fa24b02a77f4e0f4f327e703ea8f0a2e3e131095b646e999593dc05af6b917d94b796ccfb40eecb06bd791117797f3e304ce2fb6 1 0 \\x000000010000000000800003e1e829fe16cac32d432b27f3eb2f4e4ce5af68b7d423e77b8861942c1804c19d6693126797949ed28ffabbc7beb19785ed1d80a7264667013b3a3312f81ae962595a8657663bdd41ee1dcf9a9c358b72133bbfdb0a1afa8218efee78ec8ba828d160e70373e63409fa2d0ce13f2b8ef2f3a5eb4b321e550ec2b842008a0d464d010001 \\x2c2a04fdc2e3dd1edebfb6e015846b5edb473e1c4b4e77e51a7cbd4d80d21160b1b0ea67cc3a85385c3d5210f464c90d116e51f38ecaa09e8d2ff93c6078df00 1660733441000000 1661338241000000 1724410241000000 1819018241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-211 \\xfa69f47bb80f791c8bf70881534729455cda9d3cb427a5007294e7890a4c00cfebdc83db5c6f6f105f07c658960d9e3f245cf220e2250303f0a567977192d0eb 1 0 \\x000000010000000000800003a89fd05ea18a96712afa66ffd65080650ffa76b90f12efcae8546d105476907d346da52523aa19d2e6018df361b20030622c17b14f61f86c2f6aa66fb92a0622b3588b616f5a725694d0cf4e330e2f0123922ff47f756cc511f1478da9d8fc0d2c24f10c15e6f552f347aaba07aaf3e13f61a890219dd55007c24be9fad1953d010001 \\x1396c5a098f0374eb2afe76476092d604edd8bc8396727961101f3c1eb6981b74963b54d71a0bbbd806624e6cc922dae629c7011e83ae7515980178306a8dc04 1652874941000000 1653479741000000 1716551741000000 1811159741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-212 \\xfc697dcfd2555af070892f89d6ffc2d614d344b8c6d7d066b414d7bd94860cfc4e86287d323ffd6e20d85a4a1cda95c0d61b9dddc66ea48f3913ec0043c3141c 1 0 \\x000000010000000000800003bcb38a8f24a8c418ffb9c33372ece4b461e4e158974e50092fc66ebef5b33e3b7fbe927a3f0f059bbadf6f2838ecb776e5bd4a794ae3a2f8dd1a468555204a6ee857c4593fcdb88ffb864c32bb94f3814a106c6b8255776f65482c92450df261bd78b15623a46cb646df293628651129a29ac59cff33ae2fbac239b0683a4d65010001 \\xaa682cf853d11ace55cba1d58a2585b91dc548edda814549dd15e416b4e202463cbbf3b73ab606d5ab739069f626aec352b6904ae1ab4e728ae9e3f838e91d0b 1650456941000000 1651061741000000 1714133741000000 1808741741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-213 \\xfe91f22143f2c1849eac7b27dea11700f185e6bf8345517830d7d956dab181bbe550cc5827238ba46bbd931a053ce0237c9c6bdb295965c0d6ab57652e5c1c12 1 0 \\x000000010000000000800003d34b193b0c515608f41688b89922bd9c247025f23134fae09e4f4a8b7881584a6bdae53e736389c99923d8e3d09e202362670dd3a0e7e20071bd17899d1db2c2bf03f050ad29a3c992f01ce06573a226c6bc0c2415ebd607f4b4e52cc709bffa1257659346b5298974c539c62344957e490fb4b1e828402d6edf55f444b91e2d010001 \\x93aea323099fa4a6ac0c26372950977e2fe58d785ccf80aa6a4866fc74ca86ad19cea6693d922d61b732e7268a5163362a94624c721c74f2754ce406e719e30e 1648038941000000 1648643741000000 1711715741000000 1806323741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-214 \\xfe01e45ab30326b2206c0be1dcdc4a30024b8c002bd3bd298fc70be69574a5031072d17e667d93c1e71228c48f902d5803b441fa87db5a1ca5ad14a5ff5e0ce6 1 0 \\x000000010000000000800003c5373eb6544319317c137c24b0c6e001d5c9ada1828c69e6761dc93655b0715f0548c50e3665a274b8e7b49f7216df8f4dae3ae9dfa29c2aced963133987c737812ee06f37b4c634c961b02be5612a0641981f1cc2ae85a887babdbdac72fb9959e7b2865b440cee15205237032bb58d7fbd37a72a8fbc89bd389cc95b55a4bb010001 \\xfa62e7fadbc19cdef48fde529672d578f733b4a24884b71caacb3ea1e038b493bf49a843bfcd7145bb234894f9f2c411dccea04b92e47be77f8d94cf46598f06 1668591941000000 1669196741000000 1732268741000000 1826876741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-215 \\x0012868383ba21b8026c0fa01d980f369e0a2eee295629005ab2a5f7e11bb2400e37e09496abc1825177e27827c739cd2b704f6a6bf6c6a4d4b41085435c4ae2 1 0 \\x000000010000000000800003c6f4ec1b160be9114db6379b8147ecc923b75772136018ce93e78e0addf877d45fcb6f87528188fe8996852dcae339a3afe0070bf145be40497e8977f919ed267249c8119683aba808e0b5ea34e60efb910e6583b43485c38394bb8ba3ab483d7ebebdf183110468f3c02c9733bc9d78c4921b6609d4b15f93dfab682c26bb99010001 \\xf3e588826874a0241c7437d1fc4e9dcb230b66a1e28a746fc0664cbde5615cfc805d4239d95c3be669b38e101bb67025ea9f988a2201a2e8972868beaf6fd00b 1639575941000000 1640180741000000 1703252741000000 1797860741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-216 \\x01da62389b39faba35b7683539f082bb5814e347ad038f6b21121d493e1c57d1a642eb6686bb4e8dab0d94fa6093de79f3a89cc4364d0227aa3e0269d2fc0f30 1 0 \\x000000010000000000800003c4479744f4c633b63d067ee9911258465b2ca78dafd6f11bbb89c160d17cbe09a3946b7117587fdf1f1be5bcfd9ce224e7247c2ad4b1379ed8a15987fae8a7d01ece636f9b92b392bac9030925d105c8615a672ce9086e0100b85a618b622e626752b3bf8c64a1b3ad444685bd72430a8daf60ef51dd3a2c483a967e109da223010001 \\x41dda9476b04f6053d3d10e89976cefd9e6d4758a574af22b3cd11495bfa83e3d2a578583a6aa7f3c70938a8815bcf3cb4b3863e1dd821c0221c2fd6bf46ac05 1661942441000000 1662547241000000 1725619241000000 1820227241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-217 \\x02c6b0ff00d9e857e7d6b0adcdf8dba737818613583a35a48f0719b5963b5b1fa3b22d2a517ef533446295ad40c8e476ad283e7c1d6aee0175b8c41a0c852e1e 1 0 \\x000000010000000000800003bfe0d766a573e39c22b77605956bde6f9fb00bb734beb02d65e8ca9f2f805db63307839f5b6f26a90ed49b00f2319a0c1302260ceb0cd1c7c3d1ca0932e5f8d85b9cc514c094927a6f06a522767dc3e133d46e30ca32aefae41f637650e0686fd1f8ed9cebaa45a50798787ae1c9274f8f14a15e0a6b6d8f2a0883e5faf32e07010001 \\x56e810452087894c69ce7bad51ebc899bf380da6d4d455c90edd6eba33369b67e07952367dd01bad46c5f30362113216f82a2ff5a0dee153615a7e2dc52cc001 1651061441000000 1651666241000000 1714738241000000 1809346241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-218 \\x032a00d5b8a76d813d926f3a634c8b18f09d9937e6e29240d5c74c91dfdfb80c75ec9fa6db69f38e600628fb8e36a264bbae9e2e1521bb84078592e2f488c21e 1 0 \\x000000010000000000800003ae2966d5d650842fdf28e479aa550ff169bde075addc78203908ce4b9b308d0337186d77def5f2f7308d2340070fca35bcd3a24c9322cc7c779fc8f1885a3666e6a24fbeb16219f643a134645d5305603733b95c0a73a086edaaece843a142dfd2c167b6d305529b0b70b1e77f8516611771ee27e92ab56e7bdd6ec7fb581fc7010001 \\xfe061457c18f7674a2565aa9dcb9e88a5a3aa4973842b65e05290e05df1ef046897c4e15fc040462d4c8d75142150df5328de5e3b5fd81d7a57830cdf6945f06 1651061441000000 1651666241000000 1714738241000000 1809346241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-219 \\x04d63639e36f9cd27b974af72240dd21a010e0284b668b67165df67b96a03d241be5ce2b30338fab2831406b6c66b8f97a96d655623452b6148ffa708827b106 1 0 \\x000000010000000000800003d8c322433b26622e114ed3f642fb2a2d44d9828f1d60c7604feb836f9818ef5473bf721b20abcdc27e1858453389853f11cbf871107080cca88575cf727e2b8e4a38635777a2f882b829b92e02235786d8d6177dc52e8f8603c667f1aac31c15d554110a4bec179074fe4507cb0fd7fe337cbba1dedf8a12e3ae43e0cfadd22b010001 \\x160a41fd779e5bc03e0c08d366eff721c9a98c77b5a7f4a8ea48b63096aaa17a605099c9077b9db0ae653a4a0881c045d136d6f3fb616acbf92b2b7948af4f08 1667382941000000 1667987741000000 1731059741000000 1825667741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-220 \\x055a862e91b95d1f0611ab0ea0a8edf8289a5431e3b6e9542035df4e96f79eee3e6895608d35fb100b906d2272b257bb6e9a95f6ccd010c8f2dfd3a9bccec2ed 1 0 \\x000000010000000000800003cc23ee0e4b43b0d4ee31b3a2e568421e0a9dae824a78dff25259c66a7816ecdde1a09a03734ed69751c0409bc5239443288b5c4595fe729d1cc45abc16e1d6a386ce07ee8072cf52a196eba6016d9ba8f633241ea97b6815add8dc507713debf08e472e0d7e9aa9f902d7be93f7f9119e0c9055fae7fda10debf3e09641de8c9010001 \\x5ff222eeb1e88acc2dea98c53258e81a9340165eed1735dce6b6311fa94812378ce05631a59b809d9bdeb5a6735b09b90fb9628025945a8707d1bc40b7009d0e 1666173941000000 1666778741000000 1729850741000000 1824458741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-221 \\x09aad548e5db208c7338b362e3dfbfeb0d95165cc38f79c5fd574fea61d2314a9f8e69d73fb96220ea8af9898d7cb46dbdc02863fd735e41a817b2e73e087b77 1 0 \\x000000010000000000800003a210780c72fc911433b8ec234dae74a08f19680e68abd6d89e7809f0b6f4828096ad5e63c8f13546ca37c4a2f59e54fd56c51c0a7fa23f887d9cad4b16d7f1e990a91064e510373ca88c0f80a5c40da9b0449cec14d5098adbe27d38020c9be0182e559c8c9b45fa65d3790eecdf4e9c5867837b7a646d030456fa6f5dff5edb010001 \\xdfad6bca7f63de24637547befe31e16c9fc6d581d4f40029ee1d1e079628de3552a1a31c8eea40c403ecbd78914631e83cc3e8872d6f7b2f2b62f03f2a99ec0f 1663151441000000 1663756241000000 1726828241000000 1821436241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-222 \\x096af474f8aed5fb87a211485644305ba5493fb9faa852f31a1c5dcd123738bcf03336428ae57bff370c3de06cd359804b65c759b4a9c5fa4f1f134f9b0e7657 1 0 \\x000000010000000000800003cc5b784ef543cb0aab39317d63b9d15a9d6f55976ca1ded58aa8bbb334061e9ba301247af457ffc28334dc1c1bb854ab29984ad1bd11962372c9505c5a5502c1f388f6cd7f8be6a606a02e742ed21df72dde71d5056afec2efee134bdc4d5904581948f828b61765faf424b93f2446b80cfa004de33b097515e57c58bc7a9c1d010001 \\x934b664cde441d2c791f7d211059ac982cd1e48c32d9d8fb5661e89205d984703b774506a0960b881fd630b47afceccd600740f39ef1bf5ec2c4a10bf1f9260c 1651061441000000 1651666241000000 1714738241000000 1809346241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-223 \\x0e0a87e363807270fea16a347f369937d7aef5c9060a8fdb5d84d419970e435975997f4f5fbe69f738315cbacca190911935af0bf69698f5f5449eacfd15b06a 1 0 \\x000000010000000000800003beb5dc33bb091eaac7991dc08378215aa5b089c8d389e704d110cb5f3027584a3cad434306e01f7dc83873f5f34f9fdf739bf7b158a132fc17453bd0f0cfbc6643e3a9ed650f6d9f70244f70066dbe77b2dc7a57f851b735d5df9c076e3c872beedb3254b156f551156321333f76496516f8f1e55f7fcd823c4dc298e3fe78a9010001 \\x611a4bfe5a34438107c385c203b7d192d3d106ed3d0da0efbf092acd0fcc937afe1481ec22bdd660190eed5b4fab830b72dd2333a29ca05c3d064c2c0ab1770f 1640784941000000 1641389741000000 1704461741000000 1799069741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-224 \\x0f1a797c1b5cef3bf69c2ef83b06764469d266be086f93862bfae3d7ebf5a9258fb6bede4e7620ae7e577f37647ae64e16dd1cb48be657b3fa7b8f98e1468307 1 0 \\x000000010000000000800003be8bae02f5177196da7463ff97de323b2305f0498915e176ff5521fd6c8d55a4fc6e10778ab79c636c959a6114fd0a13676f430ab31926eda04b5a2e50b06d75cfa472c644d7a4413e7e3cc963da49e6bcc4f0bf7e0910695a2f45cbb8b43cd0d55d5d9f209e27338779181c3ecb46869900de4bde9920f88ebd3605e698d177010001 \\x43a7bbd725d73da16de93551c671706a15c606566c841b503f92bab22e6ea9921155aa19c8887d6f75fd62db068769940f9155e8a678df6420c6a51b75167f07 1645620941000000 1646225741000000 1709297741000000 1803905741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-225 \\x10b6bff404f1d4bae66a74c1a9018a96a385d0b55fce61f65c0fa59a12cef43e3d9bc5a5906ab19fc336c48a5d311d31a940ccc3dc45a9292e6ff93634651a0a 1 0 \\x000000010000000000800003c83eba52687ed0a5606c3206c4d0cf102eff1136a0b89fca53e0b9660064ff3ff5e37bf63e6ee4120600d34d60485bb7003cf42618533f94717ed4a35debe2a7bb6b75b875549adda15c1da5293422ec8affc088668f3e6c682902f7b90fad0798bc411450dc2b1af159c3bb7a7e6a4b791d7d6dda356c344782ab3a25be3f79010001 \\xafb7b92754f93926c58fe57e565b73ddd3bcbaec85ad2fe52323c04fafbd98829d7cd18b0b9dd496078d97298ad3ab5fa3d8861d61475777a95725d038e5110c 1660128941000000 1660733741000000 1723805741000000 1818413741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-226 \\x141e0220f7584a17b9d147e77edd1e7c63258d522e3bf81f6e4e1101bbea444ac610dac35ae7bcc6a713133743525485b2fb8cd46da5a742af7399df58afe1ed 1 0 \\x000000010000000000800003b82e0807ccf33ed808d72c1b444e01f840bbad1760589450298ca8c51cc149beff940cf3150185d13a968b906df4ba5cb8faff5a2ece2904c56010913936c80d8f7df4abf1f6102c47e4e616e3755802f5f0eadd6c40114583354509dd9d8096e79e5d2bb21dbea0a342d9af449d1d75e303f84fe7faf520b4c1c8314ccfd375010001 \\x3c6633a64f8cd899ca736b2eefde4faa345aa1c05377b1fb22d6def798243bca7ec47b8421da3ca9d5acb3415afa43ab132eb455f19237d581db50db87bd7d07 1663151441000000 1663756241000000 1726828241000000 1821436241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-227 \\x14f2e501b6214f8a7e898219feadffc647ec5d6a831a8b1c06786384aa8b71349d9760fa8c4b0e0ab5aac9221b5bdfd33f2cf86883a7cada3a93411b9b65ba77 1 0 \\x000000010000000000800003afa87970be306f6b1fc1e8c2054df2385cc744256730207813f04688b40725910d01b5ff01529309a1565ce7e965839df7da1ef67d6739f8248e230a51468632c7504eb1a361bb771eca74d26a7e179d79d2c7e79c2e3c14ad916c5f76633697d1fe09d3fc22715850d505bde0824e5d821d10f7fd778c40d594044537b35325010001 \\x787c4da342bc329839ca89525de8e1dee380cbcd346d32b640c42a20eba007bc9918027cc06da2702f5db24a4c77ebe3e099c09da64efb084db00eb90402cc08 1649247941000000 1649852741000000 1712924741000000 1807532741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-228 \\x153e9553353f824e8d0e9c64839146ad81fe1e6cf5d21d7101e857a24433713059ea717f1088e11505915dee279b6568d79ddb33420b1f0fd64c4a141c7ff36f 1 0 \\x000000010000000000800003b49f44008e67065088a98e322b25f383fa06a2be870ff51245d330da8f2ff1f30edbd46c2beccf4ebe554b4a6f2869532ff837a4d7328ec1d4f89d6180e712224391db03521ae86fc63e5275c3b6ef46a381b0546fba7fa3dce777763bf9241367a7337121d097bed5ebfc0b8491b00864c99f74c9670ae828300a76f357d5b3010001 \\xf7b783fdb352ceb4fe5bc263d6efbe7401b75a0319824a5215672d17cda9a633b0cd894b2cf041d177dfd0f0467dcc48ccb2f6067aae91e5048abf97fb07210e 1655292941000000 1655897741000000 1718969741000000 1813577741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-229 \\x16b6883369e94ca2564eaa5fa40e2450a3fff02b7579aef287fe701cd98dd4a27b05188f82e60ad0ec7b803f985c3fbb6c2fe5ab478f2148c8efa23ba2d2487a 1 0 \\x000000010000000000800003da5a08ff555741b18f0fe3f2b42693a8caa3628601d9f5b0763f8e60c9a7373bd9c8a94b05683abc1aecd64df690e6bcb59935f2ec4489a6190a7fb4a860f647542f730c2a32efae0ec796b9b44bb25dc47909ef1c6cbafd014ae6bdecf5d75e671a50faf7e1567c21d74c720004f1a55d1d3665a1207eeb659237e6292e90bf010001 \\xdfc90fc75f170eb1b50bc4003acb0609c9226a5af3cc9f33cf045c46dfc1355bf3e7847cd527a71ba8682f4f70c71da5a7c70975ebda25d9b6fe4637530a060d 1657106441000000 1657711241000000 1720783241000000 1815391241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-230 \\x18ee89d3ed6f775ce8d5abe4a4dd76ec6d53915102e528b05e67b3cf270ba9ab6405ec7dba1efec1e7808d098c2f42d2a02c9eb3bd7069b328bf640af88d780e 1 0 \\x000000010000000000800003d052ebb20352ab45ddd743c0279b972d346a3e851faf2fe66028e91c8f8160054ead38fd0c436a37a8ce54aaae387de85982faf25386aa6077bdaf01efba17fff4a1adc7747d9738bc9cee34b563917685c6907b529abf65ca225551d4dc658ae0e5221f11e8044b48a503f2fd1e54de6818590c48e48fcd40eb9c5ae6f704df010001 \\x5c0a6521a61ccf0bf8dcec8e1f9d83674236243e43972943c6bd3bd0d5a59f9649405cce9491fb3bcca737275c69e395ab1885e8be35b6e9cf657ec5b7db5208 1656501941000000 1657106741000000 1720178741000000 1814786741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-231 \\x18ba4fdc41d7ad8f2640f956f61c2ee95074868c08a185acd24dababa5db98bf2223fcc229217407dd939134ae0f1afef0250c3a3fae98777187c02463bb97c6 1 0 \\x000000010000000000800003c918870fd0692fed400974a0ab47ded6cfd96368b1a1addcff22487c246e9492526228698d05bae6bc65a8b254fa29cbbc297b396ee83953e1f35f58b387e5229c23eb51427d6a0b444098b668c0ea1f8dd61833d3f2acb9ad5da377dc7c1f634c2c08cf71ffb53c27cc531cb14a5610fbda5b7cd4970a840e951192720ea42b010001 \\xed4747663e16a89fa8fd1d742c5fc95ffac5000d9a53e6365de0f93807ca2f9d6bf41e902fadaa4a2afa71051c7eb4e808419f645f0c37751d8ce1d435d2a507 1661337941000000 1661942741000000 1725014741000000 1819622741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-232 \\x194a02dae549dfe0ad94fbeaa03f49c9a6be2bac00b8e644ac744d575ec3db9d6ff9a563872b8bf65ecfb1bac43c4c2d37e0d342fa8ef6b647aaf50c4af219cb 1 0 \\x0000000100000000008000039e53ee9033d36ef10247dd7abf6f54cda7792324b954ea342210a348b0a789c8226418df94de2b0f7da82916dcf4c159314efe6b9c2c52a62cd0b88911e613d71a2861fc37b659651d16e861421257070a47b06bed1f1aa7fd3efd560536925960cef0073fbab48d9299c073ee46245fb7fa6ead84273532ac222c3a6606f25d010001 \\xb70474788791a6c1cf5d78b5d2e5a7a8096f8afc141ebca02d211af8f0bff75b4c8f64a82a00efe1bd429999673900708b60420e23f79b251181fc031148340c 1658315441000000 1658920241000000 1721992241000000 1816600241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-233 \\x1aee938716fa3f3129d6047f7a57f86d4f3efae960c455fc6390daf513871be8b1ac52b157da4b6b5d560699aba0eec58de96f88219b6c70620faab93b673894 1 0 \\x00000001000000000080000399e691616aca1eaf81c33f28174dd1ef836787e81c9fc8d4bc504dc6b0e8440b53a55c0a7e9ab760afbc1f64b3a8801b5e0e7fb88699f5a889b0c3186df8d567ad18026ef3295ddee85c83e9a301aa092547cda681d43c9be7543f01b80a7b662e01f189b6fbbc08318e04c7d6c7408069d6cf7d00ed07b19f6dbaa59ad8bb93010001 \\x50041cb1613f86567f2e47596a01e8189ea252185ceb3b61427e311a322bae5699976512e0e01322678bd39f98f1a1605b804a4fcb00cead7f5876054b7af206 1663151441000000 1663756241000000 1726828241000000 1821436241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-234 \\x1d82f811e7321820cdbbd953043b767fba3922a0e65abb3dfe543044759bee70df9ada9ae062aa9fe077da92a5f00a3aad6d77064aca6144ba7691c186ac1527 1 0 \\x000000010000000000800003c19194be9fc9013e5df310fcf51c42f82a7bba7705441092c9cafb2d9231a49a8d645dbf8e1100b55800c70d54c2158519eada38514d4ccf38072b971b0a0ef3596d71e005b8e9c6f7fcc66d92318b85a455203a8dd0d9a4f7c662da681733f5c1e3c0d4fc61c78d1cc884125de4ccd95b96a1a4e1f8e1c13e5708105e76bd73010001 \\x6b3b5b125792a21b03bc6aa5419ef6d768da9b959ac8683711a97bf92a6d6406e398fd8cea983c94b3bd7049fbefee5672187bee8ebb3dd1dfedfd3e6379f001 1651665941000000 1652270741000000 1715342741000000 1809950741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-235 \\x1ecedbd48b689b451a6cbf5ed6858f3ca7289aa838d4ce4df7457bb73bdf3752e551f87af89baa009340720b0eef892c4864f0f0a7231ce749a6605cc5cc0184 1 0 \\x000000010000000000800003ac4eeb280db3ffdcb9502b792ba53b4932a8c3953504e66a89dff89732567e652574a6d73866f3a8182b17cd62fb9166cb7fd8663ba3ef53bf4f0e03232a5793b909fccf51d9ddbcf83d38ba14954238234e814d6e68f6836a3227dd3c0cf083b4515f26bd7c4765040d0c1379db49bbf3abde5ffefe2a5ce4789dae29cad3c9010001 \\xe8b00c2af3f64b5a018be31325c1f2ffd70e50829ffd2f918c7ebaee4b08ae2a888bbd45adf36f877b9740f9aa0a9287c5b96aedec957222c0eb709ffcd1cd0c 1667987441000000 1668592241000000 1731664241000000 1826272241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-236 \\x1f8254a4de23d2bf2d125a5230412a1d9e743ff5a60ff0906386762b132c8c87b1fabea17090b764f291b2a8d20596beb93e2b3b2dd2abc3002de4a0929e4a66 1 0 \\x000000010000000000800003d926cd2e488c819a03c109278228ea4c7dfa0c43b818d2942ed03483d0cc87b947f8805b9c3991004c014093d477a50a88bf1bd49bbb34a4a2c83839da82ebde5b8aaf924b8750619e90927cf779d8a9fffc7781d7ab5b404aa794e3b773a0919338faad624a50cc3bba4be5f62b43235bba3349ca2d7c202689473d7b8746c3010001 \\x5b896f0d45ca472397488cf4167300c925a23011eee37b18d013a88f3af4023820a12e79660a63f6239bca2f9eab7516007627353eed55efd1297845ae1bec0f 1658315441000000 1658920241000000 1721992241000000 1816600241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-237 \\x23b237101912d9f47382102df1d60c5f523dbab70d2f889055ded288372483ccefd9007938b4c10197d1e6f87409c79dea7b307e714853d12b01a5a2c3b61a3e 1 0 \\x000000010000000000800003b65cc08ca2bdd6c34bf3c7984f855b106c44c087b022005dc44ee0f3c1a13f1bba5d9f10d6c94d623c3714b8ca503e164f1c73a3755925256a95e11f2cafba9c0303633f7abcb71219a3f7b22bb362dc254145f6e8066f549d088e8acf30f42cbae0866ca68a44b5c9c26378a02120f4308e748a1c85c76116cc6ff90f1fd467010001 \\x270a7ac707cd4b1a192842bd14e38f8cf2a3e83f95f0bde88b0177eefd960f2196058557d91db828b09382c577760d9723972b7f0f1965fd0c5a4a5eb1479d0f 1641389441000000 1641994241000000 1705066241000000 1799674241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-238 \\x23fec01729f22464890abf22a15288bd950e103a3adffaefacd2339af0dade5b374068abf7c1cc76c87bc70a75fa2b9012d70e9fd11815dbd0fb4631673bcc07 1 0 \\x000000010000000000800003c9f53636751502b4152952c5aaf212ea42ac634f426ac0bae692211d805091ad68489c2bac2307442758ab437f855156a14f69eed333e965028cda8fdb92d07c110c03e74a9caf2676e1795d9984ac18403c20f88c382ebbccec5e64a17dc6b52962dab079f274a68df9aad4c9d25c1a88cf71720d29126f26e59e4870d2f659010001 \\xdd3fc3fd047d12941c90f9659e42ed91e9f668e7cdb12b1359c12018e385dc352ce58711a11566e005c109e74b684bd78212cfce7ae92b5bbdfad9d2d03cfd0c 1660733441000000 1661338241000000 1724410241000000 1819018241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-239 \\x28be479f034d07c6d72992319dcd5de8967dbb99c8e33344214eaf07214084e01335fd68eb796b55034ddace8944b7f0b65ac3693341394922579a34abf1b160 1 0 \\x000000010000000000800003d4e1c6c331a11bb3b699df64465b3d74e0bdbe8d296d1b6c91380b4e329fafe25dbca7956a5fc7180a29dfb76580d1806001679ec71db2f43dc05f251c375b4216077ba14d0cd6a72f4e25fec75b685187a5572fa4e8cba972fc7bec1a56d2a286bc6a64db8d56c391714bc06407d2cbd0e22dc105487f2607d98c3ce27c0385010001 \\xf46efe6857909e5a5e3b22fc3b43e6c73cd3dc25b7a201481d1f506f43e377a45c7830bd4a21cc510ca0f2c55e462ff9ffdd661419ab19cda2bcd7f5eafc970d 1667382941000000 1667987741000000 1731059741000000 1825667741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-240 \\x2ec623dc4bfa18789ea4e7b413f6fae12362ece2d77bfde444845099e411ab7600fd468cb480051c4b9abafe24d07a79d1a2e520aaa74b9e4baeb19091938f6e 1 0 \\x000000010000000000800003ce0971f1b0ef392a372dfd2838c7878f33e071d2be708e6399426fb3eae5a41a3348218482599f597988a1e01289cff26a10c5144fc893224cef68099518663f4df0877f3eb105711edb8923d91549e27c55fad96788840d3e4e08280c9f5fccd6f96fe2e83fde62efa2c992ef1655501729d76c8609feede29ed5b1eb827637010001 \\x9e874660cb170ec78829812de4bc4cb0e83a40f20fdbebc3ef961dd71c4d611ae45f915b22c1c13386f65a1d33041da416aaa9a8d22fe19274098cc041f2770a 1643202941000000 1643807741000000 1706879741000000 1801487741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-241 \\x2fd6f35c4ee50ae6185fa319597b7e5ce1c2d7911f7f39e23370a96a5eebf87614458b49ee4f2776183b5dacce0a5d186b66c366819a23707b182961f7f10fab 1 0 \\x000000010000000000800003da72e75b93d251e272f884524d5a9decb2014d50818ac766816385adb5e8216d2e17dfe7b2ce86801eb3418b15cff09a6d5d5f94ce1a2ff78c044c98c95d7e5c284b58d00c4607339c079dbf521b638be689f0a68d45bd453370494be812278442a02625561237052445f7e58744f1597600a2ca6b037d0e51fccfc21eb1a2d5010001 \\x6b2c4a1b2fc90851e4791b9cdd5b251ba99c9885a0f364bd40d66f76ce50e28c40e79b73aefb5a5e80469a914130f0286ab6437bf59c84aa0bbc07d5fde48c0a 1639575941000000 1640180741000000 1703252741000000 1797860741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-242 \\x3056018a8d356e73793a173c80e675424a6a500d2952a36220a9c59e18787ac7d80b18dbea42bcbf0e3678bd7acd15c2996739c4871e9514592d66725b14254f 1 0 \\x000000010000000000800003b1ae921f7207e9240298074723f663b808e192c62b5b2698f30bb92a00b2b204cae1f277e1bc975563cb516bd6525ee667b70fa506deb3b2c81eda3e49390913ae67ddfbabe03b5aabb3aece57cba3bb5d5ea1ac10f4343de4152b3b66f24fc81d41b73e77c7d544ee3e9eac09ff795dcd1a6967d2b6fb354e7be79a1b596385010001 \\xa1a0e91754f917add34c969f5d02f5861d71e53168262e83c653fd3727db13effee8db40c11ee797db474f2b6f4e1d05aa8013b6146f53281e05d2797f9f290c 1638971441000000 1639576241000000 1702648241000000 1797256241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-243 \\x3116af9789c9e2d6f5ae43226e42353ea0a3b8ad8503edae02bf72f90a78e6ddad177bff70e47aa950ef815d8ef27f8f741cc3bcbf544a04e4ad60f8ec97327c 1 0 \\x000000010000000000800003a50bfdd8e704b270a044f26a01625d6745430fd5d88d1ed63f5dcdd602a2a61bbfd47c880870d84e8e1a3020ba9563935b769ecfd6db55434b52295c2734da5f105029d71e8fcc84e4bc400154ef4691bb4a703bb91f6142fd703fbd8751e68189ae4079b1b32e8d7d5188c8349e0c0921f86df6c924af93704ca6fb825f664f010001 \\x7f93d3340a1a3eb7a895204fa3d01386618898559b5a016fea4a27e15ea89a0a05a08554d1aa4bd919d803c8ee96f8c3507125a4cee2f8f46d24aff24d18d206 1667987441000000 1668592241000000 1731664241000000 1826272241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-244 \\x31c2f7d67587f3fb3ad16c1912c98209ec433e1a2ff0b2e2a75f2ca07df48d1989c96a74ef7471583b7b53f8de0764c1a9d210bc4f84a3dd138fc9fc19553582 1 0 \\x0000000100000000008000039ee69ae32fabdae9bb7882fdae92fc922c6e902334ef252aefdb8b46c613a1f1a718e5c0645f4c3bbc3868ff28683374e2e4225efa670f3eaabd417f632457a943962632efddb57b1b97b524b4e3840d3eb2c20fa37f93e6e9397e3830e4388e4557912b973b76fc4fdeba24cdea0ec339eaacbd163561810317b4ceb800e9f1010001 \\xf485281669b94500395e68f3c73f285d54ec14aaa3c0799b932821be4bedfe8ad63196e56043fd06e4ebe965d9623b7764162ae7234a00c1244356df8e5f6609 1666173941000000 1666778741000000 1729850741000000 1824458741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-245 \\x330ecd85f833cbac973f7eb2da9d5ff76e80e92939e261f0f3f5b1ad30f73916c2561180de83152c3a740cfa20c9d7e98797f6bbcd2e738ca27269686ebb52f5 1 0 \\x000000010000000000800003b17d995caa879c03a83fb4fadc770a539f868bea61f83054fd215b3e0486a6940a91f6f9ac2d04aa3b3505b0497d88673746edc8f7abe970e613b4e6fd2e2d1bb788aa292e026122e4f5bd8c630255e7db5de8c38cfaed1aeea559a6762a3adc60808b0a17b0bf3916b5207c671521e0fd1ba6acf789b7e493fda4ab5c043de3010001 \\x65d2d163dc38a8fc0f1caf91784a10fa279b6ba593a9b7582e01ad9077f07ad21cf5d7daa7e1c50a2af756c330a8c704bf0c3aafab88153dd2aab013f963e508 1660128941000000 1660733741000000 1723805741000000 1818413741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-246 \\x3706bb582858889f2ef043b34c8949741a0b36cb992def276e087afd1a83bed11f091f1121502ac34ace7ffbf3b835a9c9e75f61664b516753fdccd46b0ab94e 1 0 \\x000000010000000000800003ae8805fdb3b881c4bf474fee9f4d113061d1579d578ec60dc1a0da9a040fbccc14e64b0c3238cdea99590202d3bb8b15b3d8982028a1c3df72e39f643fc0b21d4e35d49684773d1d618700bfed884ec348c4febfcef7fdb0c2767d0e9729a0d7275288f55631110792838f15309fae1441a7f4abe9e6b0c4db08199ba933d41d010001 \\x118f4ffd053b3ee49b76022dd10f3e84ec7ac7fdf1c63778873b465d36d8e3e645cfd0575cb3751f29cbeb1dbc178226e698d76bca651f93260fe0a502c8270e 1657710941000000 1658315741000000 1721387741000000 1815995741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-247 \\x37ea78a80288c7158da10e69dbec417a2a345e14c606bd143f0bde59b9e9d0ed1891eb43a4b0d2696801ae3cea2d2fae2b5f466b9f9f4df2ceaa2f93cae6aa2d 1 0 \\x000000010000000000800003a40c6ae4d74ed80643f128231f52d2ad5e0de46b1525bf04a44f4ae8ae529300eb9491b691eb5e04dc8c7e307a9f3b182022b905825c93adc46ece4815bbdd0a28430998b1202f5822c5c28ae3d832aaf0054f9c010c2cd2704b29399369165156c05da6d50f696f6f9c2f16f7f75e1c0f44d5759cad45f0dead01b407e9c765010001 \\x13031405db731dd0a6aca035c8a82e43403a94a2e206abf40c8ec80f96cdb05717bc3e178301155474d9dfbb2158f403326aa0e97c4d7c697963703a2c09e901 1645016441000000 1645621241000000 1708693241000000 1803301241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-248 \\x393ec72b6b4c5e08afc2e83ecb49ed8f1870e29a16ae4015bc64a7b183e5d94a22bf9c15d6636fb2ca13e1d0d5cad9c8784e6545548cb131342d2ca928ec6dd8 1 0 \\x000000010000000000800003b921af87901f6b7b1d77d215e10aa3a9a360690c62bb167089ece3ac92ebc4f474650daf8764b58ff6d6d4a69f6ae6d4db85713dcb963850058b7b46c524922ee4f6b1aa919d4395ec0e5f4e168644ebcfd3468635cb9399cd53418db57abf53969a2665d696c039f820ff3611df97a5cc52a05e110c3f1d3b7c00f09e3daf9d010001 \\x987dc278fa9c358e7d6ba71129402a9a755ce7314a9ca428ede0215187f18c77faa7fd8554a32d65c57a5bc43740dd1a04e9d9fd5be087b5802d6bbca82a3802 1646225441000000 1646830241000000 1709902241000000 1804510241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-249 \\x3d62c421202297967c83758cebbe39f1ca9285802a3ce40a8b800e4f0c29ab58f2ebbf5075d1e654e3d3144fb75a81a22687a8235c3ef852c73c90069eafce06 1 0 \\x000000010000000000800003bc7c4650ca57dca6fda975e8f3a8728b8b4f4a76e850af837cb1576c184ecd146422641f5d0d5c16e5ab10aa270aa1506a217e19b8da588cecf19aeeb9b0c30721d60fdcea30920e3d197529ff57ccbd2a29f8fd22c157fb8587f5e10bf7307606d8836cfbb4f3176419a5194605bbe9634f4c25b8b79be3234cc39f90217ed9010001 \\x9d68b8379510e4adbaaf4ef1acc62b243370c0d2b392bb7dc88aaaa65dda441eb68ec0ec804ded8a82489b30ed684e04e08389cba02c039ee5a34dbc6c802604 1645016441000000 1645621241000000 1708693241000000 1803301241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-250 \\x3f6a1de02b352cd02ec2f0b0346c97b7541e73b83b37c157e65f67739ca9c8024fbf28ce3b9899d805c1a7bb9709098573377786e15709c880dbae220326e95f 1 0 \\x000000010000000000800003f16ef42f253cfdb48e98c4e6fef94c83efcb8e16cec31c8e422a13267ffc2791f8b8065a4223e1a3c4067f7b85f3f53fa8d78566732ccf392f2b07ddfd3b508a4af2bd72c1d2fcfbf705ae14eadd878192662dac623ec2dbbf113bcc2f2cdc22b43d81594cabbfd110a3107b805813ef1ed05dc30634981150d0e5dbef7d1f89010001 \\x9c2bd1bf5c633901cb63d27747159c573a4a3ee4fa985f547a6001258acbefba4b7f31253973dceb3d1ac230e682f2666af7e601145250a177c2a39e7fc7c50c 1646829941000000 1647434741000000 1710506741000000 1805114741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-251 \\x45aa9b631aed6cfd940487931a40459f1db39eb9ddc157b187dbcaf83cb77c2d315c3748dd8c9fd687be073615650006f9f616874393b0d08b5c23824a95a085 1 0 \\x000000010000000000800003cab06bece2e9ce7a0f3e0b5c2ab90981e85c131fe862aef51ab11c9b5dbe03912513f298bdf53ec5fc238afe9ec3ac22e47de17c356c0034a1123faaae500a87fd45fe53dfbb1b379fcba740b40cae448270218e262aa0578eb526a25211a3656a9f0875255aa619b3e4d84ea41356dff634e904b978d9fc6165c878ae2bb61d010001 \\x0a485395bd65ad9ad98824ff394782b2e9525135c4b84a7e3edaffd846e9ef90fae39f050b00587d7a379cf2da569dc7b4b60818eeed127998370126a84c9f0c 1656501941000000 1657106741000000 1720178741000000 1814786741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-252 \\x4f024fc06fdaeacdd0afcac4ffaaba2159a1aee41c7a906e10b28962568fd39a117d267afbf05c40283fafd8971a094dd4685244ae91bf5dfa9a358896d5d065 1 0 \\x000000010000000000800003c37635b8596c8445e4c902f65b060cd039c534af8691ab7b25ae7f43592753683b442038fa0bf5535607dfc38c417ec89d90cc0477d3572eb1af53f0bb6388d05dfb7db662836eb0c88f7ef000d23ab08958c62116171a49134a5f79863f8999067ed3a23344496d31cb6c6c4265bb43ebcc1c8a790c1fd926eeebc340beae49010001 \\xca1cf32412a61833c13a9b8b5f221a6a57a97c89f35f9744010290f54304a9e18accd1ec6bfb6d68c0bf631bfe0a98481a476e6941a491581c0544054a216a0b 1663151441000000 1663756241000000 1726828241000000 1821436241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-253 \\x527eff165441713f27b9a3dee90da355af0827142dfb723ce108025afdfbee5e50877751f29b41cb13afa8cf9ec21672c5908df4c2363bd5b59d9b3441c2fc04 1 0 \\x000000010000000000800003bfc85119d80f1386f9f2bc9c38640aec8f1827305a420646683d976110118de96d4e117cfde5109c3dc8d577a477c47d9715cec3fdec49f88ad5ad5c3f55a666cb0fd76259d14cfbb0ff8b4dafe6a0e79e40f74de710241166f2824f69cb83a2ccb3786e55f9330d0f1a6a54965712e27bbeca41f1d5c6e0ff58b78c5a3fe2a1010001 \\xbde4d1bb7735e3653de37357cf7e1419ac239cf4c8ff4424a41a37057aec7591d8053d4baaecb19290ef2bb22984bca26ded276739d6ccb6f218dc0a7f15d70b 1648643441000000 1649248241000000 1712320241000000 1806928241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-254 \\x546284c9d72dbfd761ed73c4cf1dbf033474d6b816e414896a24cd70982b74c298df3e62d3fb4bc2b7a8aa183ab04de1f88dab94765d28896faf36cc824e2790 1 0 \\x000000010000000000800003c472ae9e501da3004a64fa0831da1f9651e05b1f2f45796b8a3571e391f8d1d2e4f0d93c6f64dd2a54d8fc0b19f33d452114d1ea48764b72d09da89b914e7740b08e4f2ba2939bd991ad8cf0574238778b0e0692c6e8a80a5bccab43748c5e1e4dc9895710cb150576c826e012abc32e000dd3a53a0befff521d10f4f6abcc51010001 \\x00ac25f62108750b3f49743b94761763a2b4ca737c98825497beddcf3ddf12e619210469e76dfe805156c7e44321490d77f6b75c29d9bd95db346f20aaf90001 1662546941000000 1663151741000000 1726223741000000 1820831741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-255 \\x54421631fb48f1508b385148ce40bfc04d47f99b4480de38c08ffdb60cedf6b88dd36697dc15963ad57c565efe37438e799dc096376d9a8a585d800860af0597 1 0 \\x000000010000000000800003cd7f0c42bb0afe16afd5ca37b0be3e27e94b40d1b32d0e5025fa9f861bb324cdfdc22aadb046dc185c9dd8b8ff06f8195acc5a606d41cf5b0211cc78386dbeda40b21f77f6c0cea98ce9e8948016ef8555823450e9d240c361c6211dce4d86ef60e2f4f6aca8c1e1428a48facb1b02e9106110877434529e1ad62bf6b38053af010001 \\x8d3ae2a0337c26efed5c49ee5d0c6ea6c7f6a4fedcfc641c8018236edac0cf2f008984ce7b777a733d6ddc56413eba106bf09ffdbcdc3e0d215629341494f70a 1654083941000000 1654688741000000 1717760741000000 1812368741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-256 \\x55bead5bfedbe2407bf6a100a39ffeb8d8f847949b2e3e9593ffa5e01b7172f26e8dfdd1508da2f8da3909b3dd3e540f5989e4be84c37464b56fd0485b5b32c0 1 0 \\x000000010000000000800003d86e2783a55b3c9558666da488e787fa3d0f6e9ef03626f7b7fb9e47af4d397622ac7be5e4802e2304010849f75fd391b7a9a02e77d86e2149907ad4a62b1e4a97a95bba268ffe87f0d2c798be2c14d4b6be6c83f2e371d4a6054b7645999385303ed17485101e0cad349fa75fab366eeaccbfc82fdd6629756128659b8f8fe7010001 \\xfd4801b21baa96d5c4f6294226068853fa45d4f2b3d1c79ff8b40e87d0577c89fc6fd9219280515bd695871086afc175f060ade393976ec51db91badf6543b0e 1661942441000000 1662547241000000 1725619241000000 1820227241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-257 \\x573e99e4b1c0ebe8f343d0ed69ba772c23ed0b5854945303205d0204e07858b37c4760e85cee65e480f4501c4b2657fb132b73ed63d64e797e4d788367b13e35 1 0 \\x000000010000000000800003be76a9abad3b74af8e719ee1948ccd4c339b212cc3e93535a24d094b453d709fb5daee3599a3adf9d64143aaca6b4efdaade4e3ca388b218996dc03db78243583ccd6d78c2a0e00789145edb6fd6eaa1995800de0985de42e471bcc31e6eb43fcbd08b63b808ad904d08d2df5f926d2b5b88b88816a178ff1fd825f5b42f207d010001 \\xb1edb14cac3bbb04120645f600b78d9eab18b8deae8b5c21f67d5db4a7c2cf88afd846713886e2a229c539d06679e222ee169165973e1bcf248b936685976f06 1641389441000000 1641994241000000 1705066241000000 1799674241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-258 \\x5aca4e2259740a3d7cea924a83b463d68cc074038c27080b7483161350207bd7a58b1c00c0c91b0cdef4c22d36ca02f601372a5c2fbdd0fa715a2c8930a582e1 1 0 \\x0000000100000000008000039cf5a4d5892e928644a64789e816faf211438b4884299861c305b2635b39a2eec34573862c0033eab962a16141ea419ad662e60292b6261af37d6bfd4439402593794309ce4081806bfd1aaa283e1fa0303e9b2c67adad53e1eff9c98c798c8a64123158f20f5695226aee98f3c64fd94a3ba450fc29f8370970aa1a366bcfad010001 \\xfde763aa0a4082a871751af9012f0e5fb688c9af25a508dfcbdf375c1e371a06546b3e07ae8039efb9d1399e45c333044122006fa8df87dd06f4a2f90f70c606 1654083941000000 1654688741000000 1717760741000000 1812368741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-259 \\x5c8a5e7651b81aa702c642c61de0e35bde538f02682233752390c58a66e42251e586e3e83b236ff1c2ce65dd87b3a8946987d85299ddadcacc36f6964fefa1a2 1 0 \\x000000010000000000800003d976ad44473c354d3fe937f9ef3f54351a2ec4cc927782be3f1fa47d258e427d13bf91f533b695fe99fb5c6340d1885592f37129600d162bfc5b005b311561dbd013352133dab8ddba14ab4e78406edfd024e12ac80ee084face0434c292cab40f65cddbe155bc7d9f6f5fde2fcb0a187071371bae20d1492e34f4711811b375010001 \\x291fc5833ad269572b868e2184a5c3ea6dab686f2b5668a9fe76dc6a8cf089b06e0d8ceea865b9138f9fda8f209586a79cdd884b735832e84e82dbfce2fe0a0b 1658919941000000 1659524741000000 1722596741000000 1817204741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-260 \\x5c36da9276742089e99b57d033d9cf0493737e389a4c1a3a4339f70eec66943b34082f3b5c73a2eb01d4bf2132e0fb1bf552314759579a2968163f06e246dd83 1 0 \\x000000010000000000800003f607e63309f0db53d8ae1521031b29e4cd7e3117fd2dd8f1e89e0f477bf4672d87d9e3a4fd4977af93441144b0f92202e95f77c8ea8b208309fe7b82abd6d73c1a21b2c398c3421cc51609f4b609a41b86a160f2583fd1d940d69b4266160defdf29ca44dc892b13ff2dc7e67a014c0f8507db566a99805cbdd1c3d2a211ea53010001 \\xaaae8da162173ac74cf4f5717183c38d25ca2ace2db0c3c0fda31274c5b429cca8c1f64e4443666be0abb9490f3e5b210d2648a7d7143ef3f82a45da800bbf01 1641993941000000 1642598741000000 1705670741000000 1800278741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-261 \\x5f12c848f0aa15f7d056c8a3f6e9f2b4c0f5c2c4979a9e6c2b882e562280de846084bb750b1e7a15c78f6e95572043db153c1941f85061f44205c4b01aed6507 1 0 \\x000000010000000000800003d9b2158e4ae5945a44f1390340066c3483d1ca3f9d081c90ff0561e119cfbd8a1874274ac4815997ede8763a75c960a494156a5ee079fbdd09896661fd1daa0e3bfec38dcd6c9ad9e44e5e6b9ed24b9db924a7a432f86ab1e6d4ca197efd7705aa19f5fa63ff73e9a797ec0810b77a96b76a58723c75980f42f9c49d8046792f010001 \\xe99c75c32a1e860aceac6f61d82447f1ddde8d8db24bbbc49df263b6868700eb57b2eb2c7daba7d8dd4f86b680e6cf9b100637fdebd3b9dd7a9679536b504902 1640180441000000 1640785241000000 1703857241000000 1798465241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-262 \\x69befc01dd9ff6b272334f32b8e8d7dd89e5805f2176ba54fa00e009edc771e1c7aca41101adb6d0b4b613e73c447ff435873000c401065bb76bf8a5e81f5da0 1 0 \\x000000010000000000800003d6263c245a386fae92c305c7ac0b6094349e2ffb2303f1b96a072fec04ca94654fc1c3c5ff742e7ca1591f8a20510682a98e0bf2c2df5df1b2a34bc40b62d76d2ce069161ba5e9eb4dee785a9d1e6e69e4797274cbc8ff9a4fad4214715b76b3bf8d225e8d6289823887da2b2588101174f529badc7cfe95eedb70e0f12e767d010001 \\x48b471503731afe7722db099bbd18af7d10243ccc1eac71dcde8f95564f6b34eb1588ea6f56b64e527d816604e934c4964d09bcf6fc83a5dcc0d6fcf3173d108 1645016441000000 1645621241000000 1708693241000000 1803301241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-263 \\x6b42f02638c6ed4177ff0e6edcace85e045dd8d1f1f58416cdb8bb4365467eca19102eb28f75764e5e8998756af6a5c5a1ab544499d1d2315508d47e5f263c68 1 0 \\x000000010000000000800003b8e74b9dc9ac59e7f3a69840241c557d970e32a0075e6b8975fff34bdd97e1a855015ed6a344460927d28c6be9051b37e7b8423c0cfdda83a21200884c314aab80cc074815653fcbe35f9917621aa2c78e88996cae5999dd5ed02032c908c2e1a4b428d80d3449043f7cedde8db2b9861af0e48d19ed455c3e6deed450a08ff5010001 \\x2e14190309239a78fab5f72966f8b4b2024b565eccaed08cfd6cdcf3b8d4487537a8ff342139ad231cf509ad6aaa06739ad196bbc84d3793fe22c2a1c9c3d100 1648643441000000 1649248241000000 1712320241000000 1806928241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-264 \\x70ea2bee5a72d3ef84539c95180eb9b1d3e83331f6f1b49cc30014c9c12346c78a5fd2980c34feb7feeeb916e7db9b5b26c4593597caec569be091d412a366ff 1 0 \\x000000010000000000800003c1f6b713624b2363799452561f378d467dc2ea7ae84a14612d0583e3a73fd3d4466b3c75eba7ca9ca260fe81770316d136fb5128224e9a8571411854fb8250d498a757b4e60956b3db679629e5a1334af2b6d92283aa230545a6c25a67f8b418a3cef0f50aaf4db2077f6127f8a60e858112723a3f85f658d6a8cb3524572319010001 \\x665bf8c31505f2a500781fc3cd28c68ba8aa516f3878afed805ac3674deb326f0da6bba94076e5a9686306b4e2852d7a1dbb3b566fff04ec8aacd9427dbbd705 1638971441000000 1639576241000000 1702648241000000 1797256241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-265 \\x768e0edacf8bb35874f82534fef5c1b58f1d7887d2b8333375c4c067323cf2eaee15c9961df0ee2548d93d20c15d809b34c09fdf008e2b09689871e759b78e5a 1 0 \\x000000010000000000800003bc609c459f6751ea0c8133b4b241f4818ab2c0e52da0a0d1e5e3a9df44b1e20dbeaeae0e99f8887f24049c3c3c79ccfb648b08d4c492192f4044335fe4a07af3dfcb96e92be49005a0afed79b322e39d37da7f6d63dee75c98dd2efae164e8c83bbc137366c5bc1082665c55223591633a474a06b12dd34db480da0b32d4923b010001 \\x99e6c2a4761d4a3da4eaeababcbba54790ba1732207140280c3c4666d80e7c0f60dc8d761c67cd5c0acaee72982e48d79b5d3b0a45c35c39b62ca66cee92ec08 1667987441000000 1668592241000000 1731664241000000 1826272241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-266 \\x76ba618900d4b5be89ffd16d0288f16528c953827ef404fe0d150689db5d02bcd4af547122c7130b3c3fe5a8451117794b4041a2c2ea1d4effb1e153ef1f673f 1 0 \\x000000010000000000800003abb9215ec36ba898bb251a31975c93f38181d245af09d18d1bf9f95174fed94ce386b0b918367afee9f40c36956b0f97ff357b9a7230d31bad48a3ac7244e7c9d9c7a572389916c9db135d5a2024e0ebeeda1911eeca40cd03d064b6adcf0881148029d889cac56dad1e0601dbccd36482eef150400a9af4fa48da8271bc3e29010001 \\xefb69606d213ca1d570697725e358ceddae16883fec183f20be5631ff2a3427e12f77fe1e578d9dcb0c0bc7e4fc47287b70ca3f50855287e41a755c7f29bf409 1662546941000000 1663151741000000 1726223741000000 1820831741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-267 \\x765a1b1e95a36854bc68ec18b145905c07ca2f1ffcc2e40b3d1471cbfd8035690930421e59c5c05934531b60c96be0ae60f742d413886585d59810b61a197e6e 1 0 \\x000000010000000000800003bf709def5584b08c17f4ad0a0d8e7a6c35eeda42b9475f69813b614099a753d02c902fcfcee4d21618f40a8f26b6b3002972e1328332945d0948c0c268ee35ce869afe6224417eed6edfd931aa7d1a8eb609affed4d454448a5adf942eb92d9518ea4ef3a8fe79b346a281fdc61e1eb3faeb994663ec5ee4f8bc5a279e95e495010001 \\x85297e870a77ceeda08d003ac95c8dfc5899e93dadafa9ec1a31490186167e2dce4ab0d2cf1d08e0393f87d890cc640ba47c590dd11f7fe97da42df3c2014908 1651665941000000 1652270741000000 1715342741000000 1809950741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-268 \\x777a0b0d8e178a0514c4d7149d18a224f04ec3e6a07fb15c8ec6792d3e81bed445f916419f8626940f8606125f2800885db2b4dca3ee67fcad20ddeefc3b2844 1 0 \\x000000010000000000800003e4b11bbb4d917098902c9e1c712820b40055f5fa249dd9c91d59e31c664bd69351f06eb31d62580bd07be56a21a3a7dfe50cccf67b145eb0100f46d27a60a3903a5ff155359129899f6556304dfef73ce0d746d69d73b2735ee82008dbf4fed09ad8a8f8837a53e9d2ba367f3089735af1e65df8aed0fd447e2a4746571efeed010001 \\xd9d64cd0147df9344c4abf33fbfe8a80b266465d37116fda513f957a644416ed3e0e1a88935546b781cbf65675d944e09cb342a74329c497e715d9b04e49a70f 1642598441000000 1643203241000000 1706275241000000 1800883241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-269 \\x7856053fe685e2e1a48f0a58a9b3e1ea6e11b7e7eaca89f88133b749bc581a48b81d47ddf83edb664a58539951d77c775453bde400e4b66ba4dc5c821f5ff3a7 1 0 \\x000000010000000000800003cd75159dd9be2cf085e5d53df0ccf386c5e681932435b944be41b190efc82964fac88c83318bed07b34f92eaa8efd64f90a27835129f41d0feebc2c73aa1d022fe935e268a6d43bb69e4069ea87059f250019c603c222d7e6fe492859d9076219030be1c8018119de29312d345abc74d63c70c2f2c207ec24923157186c35745010001 \\xdec4fb837cc62e67c78b2511c1724eca0bfaa1a56bfaa9fb41c32504b389ec4b983c292a95c8ec08902c783750420721a46a99b267e09ff8582a4c6d98da590f 1669800941000000 1670405741000000 1733477741000000 1828085741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-270 \\x790ef412acba9f993d3f2e10e12c145662a9b6ef17f1cd220a4581f2c1256ec6fa76791e24ede2fb177db3db988a7d6a29cbe945689b29640730db676aec2781 1 0 \\x000000010000000000800003dc043a356eec15ba04d51e7bba4375315a5a80c46f36c13cd3fb8acb7a92b9ec87daa08f036212a12c2446eb45cde5028b4974ab2ab037cfedb5ebd48940699a09ec2d7ce9dc0def8b8e4c54ec0759e6cef446db61e5cc3b493a1906b3cd7e3514ffc249ef14358ddf1a2d148adc9761bddc847ed92b9e213b8a32f6dd46e8e3010001 \\xe6f84926cb4aee39cc57c2fdfba55086b9b32ef184398fc5c93c01562db0fc27c5256f62bebf69da79f40d99baeb61fa183b3ecfb4f8c8810ed75880f1f78f01 1651061441000000 1651666241000000 1714738241000000 1809346241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-271 \\x7a26140760ea29e3e268612c8cc88a34968254a2b1cdb5d62fca600b5926a83bff888048ff8fd1ee81f6d29724e85600aa5b85ac8473ec28fb40427688f58184 1 0 \\x000000010000000000800003b21a83c50ec54fbf235275e8a8034a7aa1c679d80434fdcc20ce2c2e95636cbcf35da15cbe49234b977dec224ba7febe3f70289c5064fce34675cc1a6836209755cc7926d6d8bb7ef1cfc90725e6f01efce9dadd0361ab5d18d19872ba8989471339f2261aa1bde60a8c3cc6d32723eaf91136250e3f3d124e5412e4a9b1ac13010001 \\xafb8017dfb390a8c2e41eb6c693cfb15d2a21e3a91b365ef8dfc196ca3125b69184f640b2aaf65ca8c17c2eaab4bdfa7b647493bb3f4073244099b139b37e20c 1654688441000000 1655293241000000 1718365241000000 1812973241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-272 \\x7d860d9f71d24ec014263fb61afb942f1a08b9d3f1a7169349c542a83dc3bf16e075467214a8cc40b4732e2bae3370613ff55eab94ab58bf00ba078ff112951a 1 0 \\x000000010000000000800003df11f951e0fc520cac820da92eb4932741e41bfd38407c7454c0734a6dc8ff147d32227667c3d1e57ef6967a074d01c98c74906d95017142407e176dd26fe4976b2710a81be3fd3de281be84e8d7eff89687411cc5fd7fbfe4a26aa3162eecd3450663415edbc4d6355bec3e855601e82ed63c835e4ab7828d06517fc48c9181010001 \\xf555896299fa8adc857cdb9165a9482aaa6483a476dc6404dcf001b0f301cea6e72c6ec8b08839dd84948af5afefb25a5f5535f20ea64bd802bda28da22b6f02 1652270441000000 1652875241000000 1715947241000000 1810555241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-273 \\x7e46786c4d2bb3c56ba930a96aaad45030946def06306af3d5a2c07f8bc65436710321623094f9a5bbbec04bbd756f0e65a52d20e5fa69c46519e6c19002cb8b 1 0 \\x000000010000000000800003dcacb7a068682ff934ddff0db1dc5d523ad518b7adea3c0109f950922930ff99b0ded35b960b1a1eb8eb817a2ece507d99daf3a47cc28b79fa0484ffed25d45c0432d9dae135dac3125550db37164cc9e73e2a59f2a7edbcbf95e3619eb8cf777789572db4809f8b1b3f204477df58b7d790b099ef2d74c59fea37d18a2e3ec7010001 \\xa97fc49fb2e7a654387749d26cfb0ae5aeb6f82b33d0e30972ed98c468c9e7268b597bb320fec08591ddc1d4d44360481ed8a4d7d008c51b49fd90245f2bb606 1664360441000000 1664965241000000 1728037241000000 1822645241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-274 \\x7e8673fc6bb89acd39c269a81c847cb14f4e92740d4d95e114334e063ac6d32061a0584b102eb2927f117745babe4c7b7a09128bc4eec98b92f2f119a090e632 1 0 \\x000000010000000000800003afe96afad81cbcaa5e644416c4ae81ca4c2a0ee022c23d5f3e2afde8e4d16c412e5655cec188e9b7af4b7c46811cdca5052bffca8ed2b7cf038ab4342e8b5c3ac8c32d7013d7561c23d764f3a23dbed3b1b52f3e15daf4f6a79c7bfd9214d4ee055391c6c221cbe4cb97ebbc6f367c0ccf32cd5f7072e7b7866398670d2a731d010001 \\xdd52517111ff125270c400e567d9fdc3bb562f0eb8ab21f095ef4054432853122266c5213785e7cb0d7fe6f4c79b081581683abc6724d8d4fa9fbf100919bc0e 1666173941000000 1666778741000000 1729850741000000 1824458741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-275 \\x7fa681a85d421e9be128cd3f7bf8c39c2ec861a1b76831c1a184d6947e9716e255b1c4a0f630d98cf086fee42f842645488927e381023e51d6de634e1a3d420e 1 0 \\x000000010000000000800003adbc1311748f8ca464b77a54ec69b42288d249447f6bba7bef1c9cc2662b74c7aa5d380ae0ddba7ff806c92d9242356d048adf7317722b8058948afb2e320ba6d30beffd2c03ad62a4e42349241cf36e921b6890cd0276ddd0311a4d473b9a4e3bd9adc53e1091af4283a889d21746b1969ca67a5494cb3e58e1503c7ff40ae3010001 \\x9ecd77a9c185a0b881a8781f996fb78674cbc5dfc59d3e16c492c79902b0e88c2ddada2f9e3df206203610ba6f44198ecad2fb8b04601f5394db4bb266958e09 1654083941000000 1654688741000000 1717760741000000 1812368741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-276 \\x82b64ccf1dd3f1e8acfb9bbff8cc2b8350bf444bbbf36b3b25bf4d0373248162b6f2bf83ea059ba5375d582a35ccfa1c1a998e40c3dbd00b9e602f36eb588c74 1 0 \\x000000010000000000800003b0fe0173094f30893089408041bdf74a5db965c7381d79a015734f8c0d599c2a92ef68a00e47f0144fbfdf420b81a459f266622044e23f138dc3b10dbef063070096936ef514b8406cece16f528204c8d9630e269299ced53673df965c9483e6504458bf4588b39ac7bb2c51a91977d39627dcdf9c33c04de34c6962c7c900ed010001 \\xcf1e45d4c58a5da4c1863604c7928d6aa43fa30328f07454c17c35d5242b22362711efc35312045036b2c9b3e7797d8cfc4cc6ac0957dbe12ad9af24a0eafd08 1648038941000000 1648643741000000 1711715741000000 1806323741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-277 \\x837ef6126239be757fc54f568bd3e1d00d528af87734e22b9430d1a670839f1d314eb0ce7ce4b1e066b6b29381b54403cfd2f555e6e9b0956e06716a845fa7bb 1 0 \\x000000010000000000800003e25c5bc2734f63cf91e5764e2c2266b35cf359863bb6380761da8e04294a9fa353930929dcbc54fc95621fea2e8b071b4004d8a7bba2f2941f169a5f465593a85eb5bdcd717f440511737e825a9c1c421647b9a54a38d63addb672b4e7a323056b5f6665201177f2391eec25cf83d1ec59b08e1d5c79ff0a7bcfe49cf27525f1010001 \\xffc2ed32fb49a4c7cfcb1ed97a2b3ff37f76f8de10c2d0be10341d2655aad0c348fa08eb30dc61625b7142f9f6f2e388364d4653e87f3822f874405cdc077e09 1645016441000000 1645621241000000 1708693241000000 1803301241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-278 \\x835a42adb6fdf6d8075bd70828e237c29b5c402bd950a968fd15e0fcc6e32dad7a3099de4e3e8de14a769b20124d51e1e7a133a56c1dd5b1c92ea282dca4ac1d 1 0 \\x0000000100000000008000039d38bf22cdefe7849cf7d8d23856f98bff20e0b556bbeb76b4a249d379874fd15327efb4b8017f1b907772c78a469193be01d4243816705c27cbb61d56b30c03de108d43a185ef13d97c378106d5731eea3bcf25ffdae55eb900d24830aa6552cd65cae3eae9c50a4d9eefc581faea3732474af189469dc1f4e341710410d919010001 \\x9b3900244d01e772512f6517757c8e60865c57cd7d150523e3736b23ab2f3d2a3545c59db87b459656fd383b668648e1282a3d81b2e7e93bcdd816e345afa50b 1650456941000000 1651061741000000 1714133741000000 1808741741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-279 \\x83ba11e0c2f1dde89d126c45013438c939a717daafdebbf4c905c4421d4a44d2b0be784ea48963c015c78d198ee9be1cf70b6a08fe8b30a86779fd48e17efd21 1 0 \\x000000010000000000800003c8d6a1c3b9e4cf28402036ce4f8b791a9f50545696839eb373fac313ed635cae53f73e7f8f5a53c8dc376f499a3838d0ef64b01f33f32062c042399da69c584749e9b7acbd4d1333b85711cb0ea7e701e1933cd85fcb181d36e2302744b0b99f251fa311d09dafa8cb92b8a19286f251785d85f68c5c2a83d8580e6d28eb01bf010001 \\x7e45a096174e2310236b7a8bb05e67fa7122ddc1cd090f489a22444b36b9722581063277bec467521747052e8a5eadddb8c05cecd5b69de17fc19501a698ec0c 1657106441000000 1657711241000000 1720783241000000 1815391241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-280 \\x838a55ed2383a9410f62c8cac408e5021097cc70a8aebc295fb578880f997cd75687e3d3968a53b88c7b4d626d1f83597a9bd8348c3ad00767dcb84468ecc269 1 0 \\x0000000100000000008000039ed6215ba5981c5aac5aefb1ab8560562dab882c370608ba4e5f129cdfc590e1a879d5823c99f9bb1c776d2157c4cd297a90f1d14f4e462e71dccf54bcb7045c6b4e741703377d20621cab66fa5116fb767502810d4af518156b1510b0e417d8770fa98a6f537c09384f558a50fb50fbc4d76ff70cd2434397fa80a1aa389891010001 \\x93483c379510c3ad39bcf7c9dc15234f21ae156e7f686d38c39de2b332242f0f37c6194b71679b27729521cc16275b8e7bf92c2379bf184abcf03aef45a3500e 1665569441000000 1666174241000000 1729246241000000 1823854241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-281 \\x84bead4e229badb24ae285896869923811ac1c919159b8f4bb40fe0e52306cf8ab8404b6e994d0c395e8f41230ba77f7556c551ba246b29992b8b941b1399cd9 1 0 \\x000000010000000000800003d8bc9b785149181172024779b73a1f0af7bf18602b03893efa3b188ebce343b74429be9f3e2c0c8c1296ccd7898a0d64fd57e4f50d641a485e0f9dcc5417a6e240efe4390ffc9af3f49e4a68bae78ee1bbc3b546af6d46fea0f94bce75946b97ea0e2cf664268d7397e9f853ce5a839e1f962f6e35cfe779bf37581457f505d1010001 \\xc5ee98bd77f6af8c904c95c6bb6400a630a7244598ce4770f576bee693641ddb5538f4d23ce01a4f03b09df419abb65b330bd8ba7311310082e7c2dc3f07220c 1669196441000000 1669801241000000 1732873241000000 1827481241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-282 \\x8752b206d7459abadebc79926a3ce2a7a5dad20b69d811e724fe75e495cc0ef59506b21a2887c2cf807a46fad8ec94eb63fe39ee48fc510019ac3090f9ef5fe6 1 0 \\x000000010000000000800003a084afa039d4034f2fc24d41a447170e4834ed482cf39dfdcf1016728c93cbf088562c2b011a1763ffe1d21c47c3f5c5d211cc95d130fd948f08f9c7e7e3b0d5c1fcaeee71fcb76f19c93c0dd32b26119d06843d0d89591384649092ee768b8e5e83ded5e95b72e2dcc7b52c011d3fd3f6c66c3cc8fae6eebf3d1e1515fe9fa7010001 \\x19bf0f73b30406ca46353736388dc4cc80560fd4992d980ae895d5522cfa05ee62d8e2474cd2efdc4fffe8df6c5579dd5125ff5697d1f9d83d9a3baf113fbd0c 1668591941000000 1669196741000000 1732268741000000 1826876741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-283 \\x87b64bd91e63364e27e5681d7a9beaa2bd06d37f1239352a4c927c9809b506128e917e49f00146a8bc9e6982c0bb0208302b291675c6b31be5136af68839a3fa 1 0 \\x000000010000000000800003c2fbadf35e08ba77f2eb50f254b64c639e907329f4a9ec3a1412c57e6ebcb13eae111666a0b8f043ed592701874e86084c80440569fbca94ab3c51a68a9361a61ffe9ef9654163b3cfe5c0a36b1ef74892d78a521c30175ef5de528400983b41997342cff8a1b513f77827ce950189f2e4fa7e77918457b215adb19d3f59bf17010001 \\x37e1d0fc9dca2f5f5e22341b7edb87ec3569059e69d37ec2936a957ad48bec64a3554223818b5e3c041fe3066375bb3041f528197684ab28901739c1e1328e0b 1646225441000000 1646830241000000 1709902241000000 1804510241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-284 \\x8d622d69934bedfa0215821a33d214a77e0432e0329e259909b30cbd19f5795b1c0e89ff8efab87e9a254eacdb4057ecf7df3a8a148ab1fc28237608f47772d2 1 0 \\x000000010000000000800003eecc597f92ace5e4e4aca10aefa33d5d56e167848ef7d0a4fdd298da45076ed71ad3980f3c0defb7528e9942911609529500adefcdbe3f8e688faa4b782e99ef577249579c00956dcbbffeb52a42f3451e829839679954498d3c7671139154a00ffae5142a2e93e916a55173d2691c7f68397c7313c9844d70a6167fb84de39d010001 \\x61c118ac2885f20b98cd561aa7845143ddae0edc3621e9fb190cb3fe1a2cd4994b514bc8c8ecbaebad88bde54ac8909a6ebe8e0698e07a6e194802b4e267b40b 1652874941000000 1653479741000000 1716551741000000 1811159741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-285 \\x9346d51acb2bf83593cf564419a5086f549f51685cbea59092a2e6086449ec6a41c80efb9786181febd1770aa787899c51953c7759740174dcf53fd306389ff7 1 0 \\x000000010000000000800003d866e1e3a44e50fb0cc99d8b5e7c3f3583d4f59743a584a337ecf0098e21dac7f729f7cb1d79bc428474adf6c22228a5edda9f26bd2090de6a8342af073c7ecfd37bd4ac242d29f297242b6a674954c19ad670cead4bb113fc118242bd6aa68fc48618fccf61325630e349962ab4781d64d88841da31dfe2a691aea2b536b377010001 \\x6cafb7c5daa9aab2cd2350ba626a2450329f2e141fc8e5ae0677bab76d3ed89d7262af3792386a4f113219a2e383862f13b7e00006bf84a1566caeb602921e02 1666173941000000 1666778741000000 1729850741000000 1824458741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-286 \\x941a8acb4f45bec2bc7b812ed0a27b16ab1138835cebdb0a7faab46cbe2ca8d166636f3d9c2a8dc52566d8d631b77fc749b40a940dec902a9fa1d0783e757fa9 1 0 \\x000000010000000000800003be911554afdfe60e9b1c7555b5233f1648739e2e0b2b7c5fa25fbc7584e95f2ccfff617c04cad7db5f290ca4c9d2aea1530f006b392962501af5e188debf0204ed471d7947529a61d906d51ec6969f9b60706d69c8b86aa90c6b8050f813665b1686623ed533c1a0a8de83ec7acbff5867b2aa140f0adc4b30ca3b91a4ea1a93010001 \\xe4439998f8f0043e3d11e4b408f02e8c0e0803f2bc5f04c5932a5ed5487473f4f5ca7539f4e8bf88395235b092adef28d63d5aaf4920c2dce4fdd1c4d76b770f 1646829941000000 1647434741000000 1710506741000000 1805114741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-287 \\x95def4a747ec641d23a79e3f62debdfc1b073eb002c2c3480ae2010cecc181b4641182ee17aac5c1809a2d3364652a4577d7ec2c326cb60096d79eeec3c6238b 1 0 \\x000000010000000000800003be3288605c8ad6a2304e62f38ee39a5e9d4f237252692bef70afb7571835284f38d4096e38b8b5acc4584243663160e5729a4bb2aa3f6d14684b2b6e9d041c10e615d3026346f0b8aab2c929543af7d6daa341693fb5f3284862635ab665133ad976e59ea07a535421ee339cf2e1fbd08ef25942a36ca5004b74f4311cd3c4c3010001 \\xa773c54be0f174507e29227c65a23b0a7d8af9321398e20abb6671a854b79e19c19fc2bfb11bbbd56b907f25727af2a669bc3e1f1568bffe9776260e29861b03 1643202941000000 1643807741000000 1706879741000000 1801487741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-288 \\x9a9281e34d3d1d79d877016a89bf06b48e3da601306d95aa95596287c8fce3a279cdbf07afd3c73bbe2002fffbd67e78ceeaf32d0f972b864870bfa8f794904e 1 0 \\x0000000100000000008000039f736d3f62dc6d775b5c76f2e5c52f8087d1d3879aead36a0eefbf55e0b5501250240ef9de4fe869f56283ed720de379d380544bb0f472b33bdfa4d6c6cc79d70463e5e365d0b2a2185686c3819bc4993eda38b84af2684088e3d6cee64f587148c3fd812c4147f97da1d3bcc1baa1c030f74df7394b3b484a4e98cca281d7fd010001 \\x3efb80c527e91ee3dbf7104c8b598bc45cffff8c383a73ed9cc7755ea5d4084eb2bf48ab43739599e235f78fdce0fbdd3042f6951c92c3b503091b54979ead0a 1648038941000000 1648643741000000 1711715741000000 1806323741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-289 \\x9a565b16aa06bf4738b74cb2a85fe3951bd2ed08b137c3f429af5b234d34def097de6f13c246197df6f0b436b6371c15c058271764c5703b088a57c33191f8c5 1 0 \\x000000010000000000800003cd0a505d1277282834abe98d22c0cb4fcc7cbbda991b0956987618fd03720eb2424d24bee66311740bbb6ebc1d0d488db2341e240e7d7fc689383fd26bb234e93eb9e235ef4d1d50e78ecca4178354b2e1f7484b9964d325b8eb01120bbc58068479ba1c9aa2edce701c0a2d1e3508cebe53c9055ed7f83db6185aac55ac0401010001 \\x77c967d716fa0008c07869db84b357f9ac988d2400549228cf3e5c94498ed49ce1363d076d597d03d335ebcd4406f971f3744cddcc4fcab21910300c37ccb70b 1655897441000000 1656502241000000 1719574241000000 1814182241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-290 \\x9bfae3034837df6b5234eac4dfeae9be1178e54aa8f7675d8edb54bff46093d8ef2f4f25e81d4e787775b57fff89dddf338bb1b7b47d916e118218eb669b7599 1 0 \\x000000010000000000800003c345f1d816756491f76f60a326df92874307440296d67b317f421c1fd60c4e630f88c59503c243919e8c75dcfe7652ca645215b1e54e405921ab02bfba22ba7de2ec79574790a460e649783ff82bca1487394cb6bf3ffba9987d7d102d7f10fcb7894152e3fb3e3f5364d26b812736b06e4e1946a923ddf4c92a8c34d3f8e9f9010001 \\xb56e3d405cafaf5ebf2f257f1aa2966694cbf8b7ebbee7b10d4f4d6d545224685b4115aa5ef9d52623506dbeb4b8d6e92ab5a13e1e33af446950b4264124ae08 1664964941000000 1665569741000000 1728641741000000 1823249741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-291 \\x9d0a5b59b4dea721b6ecd018a433e71d1952feae934794b9f4ecc7d79feabde75d5d745b38a26eb87ca37a1baabef266fdf1a35043b7c238db8e298959da7ac5 1 0 \\x000000010000000000800003d1350d8c68e4c403293fb8ab62952e4ce13aa9b6489a476acabc356d8d7f2c57000add9761a210a47dde400ef67a4ae3db1fe02ac6dc7ffc4b93d35007795eee6489836ef1c069cfcfd473db333496e20553fab7f7394f0491376ef10f325317b0c145d2124f4b04d7545a145db131c0125c32036536d1d751a0b1591510963d010001 \\xd5722fd8b3d484c2da1519f839791ea4cc961529a4b83376892cc965175d2388c8e7008e510a2c7b28a0e99656ac29797a8bea13602a63cb27f228b7694c7f0e 1655897441000000 1656502241000000 1719574241000000 1814182241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-292 \\xa6a62410dbac03059861886b1a0fbe65e5b355abf3fd919dc8880ec99b8965f0a0ba2873806f400792932ce962618850b8abfa8574f1010b84c10aff3d52b5c9 1 0 \\x000000010000000000800003b2b587e1e4d927db7a1a843f47395d0264afeca1390214de77642899734f4fc489937d5a8f703b254c597bbeb01ba0c669e8ec4ca80767f8b12bd8149f7fcc79cf44306b7ef65c02a65896bafdd437dbb0a415af8ba3256ae14bbe322508cb3842b72fc886a077d78dd7370d477045222f27691fa55988d61abba531b09463bf010001 \\x8bd580b5e9277b193335b04b692bcbe9a7fdd9d37f0e95ae8269a65cdd5f7e4eef664a8617352d2b630c042501a5855f737b147eca612cd96b35ca7af5c5ee03 1643202941000000 1643807741000000 1706879741000000 1801487741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-293 \\xa6366cf8a3e4278e65742dced71f3aa50f2ffa48d64fc90a124d44eb93f5d6d99ede0f887678a051910846dcb53f65ba9debbd5c963a73aac74bdd6153269731 1 0 \\x000000010000000000800003bc00140fa575f7542d2a44eef63c5e81c6fca3cf610f65641ba0ee8b1141d9bbf5e7766c6a8dcc49a6b5d26fa6d59be36477069aa62ac766ccb323b243064d4c0352370d2ecf1f987f2aa042c1965ac442f8b4495db119d5759acd9c9860bf11c96f997a26e89169e7eab91c1726983ea0babf6791cf8c0c4b229f0620cc1efd010001 \\x7373377cf43828085a27ba7c8758c83de0e81e30bebd97729605a8bb1c934e72f6db4794dd675cfcbbb3b648151988e8ba8b3b16857bc87e7e0cbad922021403 1663755941000000 1664360741000000 1727432741000000 1822040741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-294 \\xa63ecf2346b6b667e4fb190955f8c3ffb6d55635ad9acec378629c8b0f184a7747034584532509d3550998f896b461e11c08d7046849c82c2c44614417547a9e 1 0 \\x000000010000000000800003f7cdbc8fb32b3f97fc3af620bcea3f9bd0171a8a368e7a67ec2d2f88f3e15b9a63e0b652c368c4d7410372956bfbd3a2c6079e1e63ddd915f246378d8ec023b8ba4168fd20f498b7597356af618342bd07d37d6cfee6018615d4bbfd20d3e0317a94709709ee64e666a0f289b24823867a4f7e4eb92541bc3b948ddbc6ec56d9010001 \\xedbb75e5026a6e1ef7851fa89da2e5bdeb9ddd0426c8ab211c7b654bc8c79c2d3aadb67d680976eb6289c941378f7bf7e11e6328340236518f209b01ab74140e 1651665941000000 1652270741000000 1715342741000000 1809950741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-295 \\xa7fe7c47748ee2721150584c6e298b9a1ed403e84c4aca623ae4d8b5700d93e01d6b01fb3c4504c868fe4f8cc4190446882088f5d4cb1c333638ec8dfb3c2428 1 0 \\x000000010000000000800003d7e57a58ad738d40075bd8d4334aaaabbd22a96d63b722075a928a3b482055a1e581d9d7649e0facabe823c09a21fefe7ec13e9568c523e3f229d58e90ba6d75f7b37ead3f8e3331085d84abda36de04d9800a3f4a29b9a06c3a6b0c49d7689756e1f4c88f34e83fbed2beec4fcf62158c11c391f7e29d2c6088c8b9ddda7f79010001 \\x46907377d6105faa5c92e67f5fbb2fa29c68d99cf7c6541ba5a6f244c359730beed57edb26d0a34ac2dc37b34787683b869bdd72b03460d006ca2f6b14ada10e 1655292941000000 1655897741000000 1718969741000000 1813577741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-296 \\xa7a292e8accc75ef9c96998d0c7008ae0122940002b070173c9cd2d38625e3c00f95ef1f7c92be57a0da2f03bbdfc7722d73b9b44da3f80648c4aa031df4cc40 1 0 \\x000000010000000000800003c8d6a798cfc2f449fc5c24d85e1c392681caa7ebfcf9fa8fbe1300cf0d3f790ff232105a8e1f864bbe12b59151dc3fbf2ce02647d5c6836d7951d4e86007f10487bb5198a4c4920dd2d3e12834d0b8939120c836a6abdec66f93ba87b3e0f90bdf38befdc185376912d88c8632ce528fbb06d8d1bd864117c41656dba2a6e647010001 \\xc6fe2a2b4a93e61c81164805244b44514e94904274dbdbc42a5fcb6bc5ea0394c890e0dbc10af823228544be4699d60881cf03b190f12312c35479c3dc010601 1640180441000000 1640785241000000 1703857241000000 1798465241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-297 \\xb2aec1afd2b72135a3414dfde13ae3e6c155955695fd72f30428962784e6dfe7f926b76549aa8cef6f7ff5229e60a489bf993747451b1408745f5b8d60ac40c9 1 0 \\x000000010000000000800003d02e559ed7424f6441e1099818c48819fdd15fe4307e1b224356d31c0902e3b230413cdf6e6964e98b357fb387eab6e81d1c70033ac889ae14634cc25d838d6792041c15ad2897260a5823b994c1190b965ce38598275a6d28184ad7a4697ae8df8a6afa6ac7fcaaa15a12c768f202347e7e4cd78ffae60a7b1e838ea0e72d05010001 \\x0b1f807687944711a4f365016123790c747eb8eae2f1d8c66622800b225d92b2b4a9fc9bef626a2cd0f0dcf9170293d2fd37952c8c2333750b30c95ba692d308 1638366941000000 1638971741000000 1702043741000000 1796651741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-298 \\xb24a8ae79df9b3a2dba1cdb505d73ffc02faab81c9c49bf3879fae28f853117e563cebefc0502c4809e5875ad98158f7d621ade4f1f601de530032da90031998 1 0 \\x000000010000000000800003e12404645beb2f016f45ee76eba3fd12b499970014910b6562fc0310246a9b0ca58c4a7cfb98a624ea503dfa9b9ea8debd7d55e5dcbf48002cb8b124d98f3db72a7149d5fe8b60bd16b5878f697f74b1a640a2be4db5a867b941c48fb876ca2ffbd296b077b6070d564ea55c3b457bffe36836d298dc3f2d512bf8c7575fc501010001 \\xd1307548fae751d5e36f49a11cc8302cb94205e298d981ce209ef91a69f1d47ea99480592ab1b0eee62bdaaf6fc7e2821739f46f6f6d1d4de404590ce05cba07 1654688441000000 1655293241000000 1718365241000000 1812973241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-299 \\xb42afa934909714dafd228c88ae998698df009b95bebe6238eb13465024d6ffa08532bbfecf669a93b8c3e9791e4f1d7737842be81aa1589cf5bcced9828fec8 1 0 \\x000000010000000000800003b1d77730b95a22f812770a40bfd0d9e56ec9870e6b1dd4efa1ff5b6081bbe3aeac3cbcaef69077554dc1a34904097d34f3b438fd552995152e0799d5f53cf0c6e001e60769837f3195769d6b8821baa2d2b21791e834489edb733e9a2d8804dec96601cdad2f8bf8534959940432bc5a35cbd6a085b9d1567335c74baeedfc4b010001 \\x2c3e7cf29601379f03d8de718ff703b2378fb83c98fefff860862597c3ba4897e4d426f85c6f3328a337864727d93fa39713f5a923c72fc00e340fafd6a64304 1665569441000000 1666174241000000 1729246241000000 1823854241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-300 \\xc016f0bf3b87bacdb42b9c9a60178eed8355b3540bb889d4f4fa2308bf7a3cd2964766cbb335f59750276417bf39f585844b07cabe47ad596bce4770a06e61c1 1 0 \\x000000010000000000800003c587bd64f9c5dd5bd15908ed37ed8ac41c8cc078defda9c02a7abb3e12900ba46fb4364abf98dd63eea5420b05db299f723328fcd2831472b0328456d19a5dd733aeed6ceb9193d6a0d1f9be2a4e7aa741ada565584a21999e80d07d48e0a0844262bba571dfb0c801c6f76929838d9ef98575a7cc04a99e0677adeed47167b3010001 \\xdf936f5543fa34fd6b896a2856e04874b7a45e986f94b3a1b0fdd416fb7f83e9c217c7bab56013bfb22e94f6c677ce62b96dd740e092d1325f646b18fec17d07 1645016441000000 1645621241000000 1708693241000000 1803301241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-301 \\xc066974c9acb37cb0ea8b72d258db8dec474362bba09303f695e69f25331af51facc5f39b10f13b50a5bc6537efd2566a9249325e8fff5a46377caf84a61127f 1 0 \\x000000010000000000800003ca8167f77a4e8d6bd5b860811262f1ac32a90a87b79114c0d02c3989fe548558fe4c57032817a8669607f12df868275c39e073bf30eb1821059fa72fc2ca8b04be9c4fcc225febd69d2f1d9bf769a7a99c988efbac9b8511c0b787c6f6a965d7dc2271b36bfa1a3cdd861337e6aa565da9526f24304e847547986c78206eef8f010001 \\x818bae0095669733c18b78b955139e7907339f1c17507c0f93655567d02c4b12b30ad154eeb796f4ea5fe96ca9af8babf397e915b189a73aa6e904921e36150f 1640180441000000 1640785241000000 1703857241000000 1798465241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-302 \\xc0e6d8f1b62e55fc70573ce1374f145512dcd34f8054a033f7b13f064b9c3e098de9c93d78c4460a4cbd58a7f77805d43494ea66a4a4a58cb71dd739f011d73f 1 0 \\x000000010000000000800003ae7b17e13940d8c018a587179d770f61aa696f8663cdb8bf0e3f50f287b2c1707c5b41fe1fab041d0ab64a1d5139bff7c0f12ea98d5c39146ef9324bcae13e3433ea277d78722ad1c32ec7d6797877e247ee9a1a9c829f899092a3c5847135b4467f2de458aae2aa6179c89c6b49d15281268ad24d131ed411f92a35381027bf010001 \\xa6d4ec4b4e8ff7745d1c372abd7426b37709a530b528a8cb9ff541becb181fea61777c796bc6fc9ad21f5525e2557fa72683a28aa23c9d3a4b46039fe5945205 1666778441000000 1667383241000000 1730455241000000 1825063241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-303 \\xc2860a37418391da98d4faabab435c12a8c5eb5a10b2ffce5fbaa76de119c2e1e23706691a667788667a3d4482f225e7c8e9c5c6ccd091cab2549c7d31dfc380 1 0 \\x000000010000000000800003c3b9b64b87639eecb3a9c39fd0ce339439d27db447340117811c92231dfcf416710bf40dc9d028d352332657002a7a06f4e89447b1a7faa469deeec810da86e1cee9d67c87c9f76fc70537cf8c5e373b30e52b7208daf9abf16b30e0c89ff07fcc5743ec14d6c658663600187c3fbc7530c780688ad6da134c74bb2343b1b0d3010001 \\x3d5a37fbe8cec744506ab2baf33a1b0e71e2f3daeae4ea583e7b28492cb59b85e5a11d2d67430e317e338f5151c7dead098a948d56674c2d8e7371b09a00130c 1641389441000000 1641994241000000 1705066241000000 1799674241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-304 \\xc272c67edf57ff9310225a536d0f3f4ecc1f9b8d1025c02804867964a1f735f64c06c926f95d14c8085ba32fcdee2bd4ee00900c3d0ecc44ecefc12e52bff94e 1 0 \\x000000010000000000800003c68a9946652749242888661324923c20a3ec873f37e3cd22d960e3af334698c959c42d80c7db1bfeb457460bc22b2de4450b57425c16e5cf79915802846c234ea5c3584fbee1066a3fb23ca5a09d54b9c5022e3f4f65d388feef28f39cbd0b3d653a5c8b25892cad76fc9dc679b8fbde3df93aa9d461bb0e389a9b914b0879e5010001 \\x8128219289247997df0d51718e01ae2d482eee84f8e9e57bbbc6f3824d95ee1bd2331c291243d66b4734cb30871ebdcb14a9619c122d1b920449cc423ce75e0e 1646829941000000 1647434741000000 1710506741000000 1805114741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-305 \\xc35ea747120fa2aef5cf0ca39c333ab8fa7e908896c1d247967c084bed4f1ee84bcd88b94773e2ffe78253d767a3e638cf07ed91029821ceb4e1a5325c86d06b 1 0 \\x000000010000000000800003d098995b3f2785ab58cc64e2a24a566eb8e0cfd2619ac76bcf5d46985cca00f0574b1e604043bb7f92f8b59f0fad0f42696a95b9a41dffd171bf24b8b00cae3eca934eda68533c7a0a26d8e11cb97641b6ad8886b6dd9fca030aaf1056e9ed94977a9b6f019efd923247b78140fd96af7190fbab9c09834c5bf12d677ed5b3af010001 \\xf17fbe26569942c34ba2c6285aa6b249d3cba7d0459f730e63532de28a3f48aefb1f8cccd9483597b526615528cf21980da83a6712f318b96e78c0ad2a66740e 1644411941000000 1645016741000000 1708088741000000 1802696741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-306 \\xcca218e3db5737b1067d6d1b02f5dd047156671733dd341376ab3df7326106e10041a79604d169d1f2b7f1158baddadc1036b9daadfc833245d22f38151e0fe6 1 0 \\x000000010000000000800003a9612c7419fc4cc6992472f0479bc6eb4f93f8bf4db40c4efbb2292808ffdb0ca961ef93f7728c8c1c0aa9d4e17648a4016275dc4e20dbe34ad8c501babda82e62c82cc57f98074659cc31607a7f6dcb32ca2579992da9307480d2c30bb78f9aa27e00d695c208375fbdbf7ab70e262d4152284b42a64bd482bf3f379a02662d010001 \\x20d9cf443f88bd8929aed66e3e1cb406576a636491590e1d339941e92eee898e3aa0ef4666880f0bb7b91fc8f31cedfa62dcb92424b2c80f71f69ec5c509870f 1664964941000000 1665569741000000 1728641741000000 1823249741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-307 \\xccf69f3a234713d6ef74b2930b9de7dac2e7b783c1700941bad663d45fb66a38eb72e18c5970a0abc2ac743af2c67c810b878f8de9f83e8e38631f7dd889c30a 1 0 \\x000000010000000000800003c1b86fdb0c2a7d533d457009690f1a59228641263c6856aedd1a968da7e004f8b3c4ea739c27352cd692a7f05f63f35773b7fe8d8ed63b5a18fe911d380337e2da87d59361ab9caf05faef409c4fddcae4dca4481847d92354a4491c472a4fea1fd4ca49363e9e6954ab807f524ec6bcab3647a36c636b599fada869aa079373010001 \\xe4c5eb9d2cdd9d4dff812f13627bf942de93af7c381b3985ea16a3f7bd374e8ccfaf377f39fed87e517c33480059d0bd5be5af6f0f0cd9e61ad8cf33e6fa5b0f 1667382941000000 1667987741000000 1731059741000000 1825667741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-308 \\xd38e43caed9790533d5e65096accb4fced5ef4228570255dace8a9b543f360452561032e631e2186918916a6ff839cd294eb6a0b93761375b7685512d615a587 1 0 \\x000000010000000000800003b4e4dc9c76d03928a6f771c618ec2b9e12814cae1e7459166cbdf329a4d5ab2977ecb36ef1a2359bf30362264e05e43988359f70d9ef77d15dd1461ec4f3c6f2f3a5e9587a639ab7223ce028a6f8a77d1c8cd884da422eb5a53519b742d505200be2e677c48a106ba9995642d7ec5140999c559aa19536812c79df92adb3c14d010001 \\xbf7c09c26415e14e8212e66c2dd4505e491627c4faabc7a58d60da47f34f534b15391c7a66f01bce85fd4b581a6d7b694c454da539dc6e83c78b5be73d72920a 1647434441000000 1648039241000000 1711111241000000 1805719241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-309 \\xd6be3cd6307340bef356d329ec8243b546698635adef70b9a97598d410422d7040c926013755696305006aa9b6f67e43f34a8db4999a7cbd6fe8ba45d48ac95e 1 0 \\x000000010000000000800003c5100fcc3724877c2a89b95b30fd42e8e725af92cd5d55a4289995f7807a93ea0784672ed8dc5dee82dfa9c382b7da80bc74749604cd18d882093ca980355e7e00f648e30ebcbfff938a8753be1ed332adecc26ab6fd2c9c6c251e30530aea18bfcda4cc0cc71744c12c4fd423bc3d4affb17ae6b8270ba1c4117bfcba4a2371010001 \\x78050c196e84b1fd79f5d7def0d173125e599cde2073d8fbc9c90950a21314cbe8c9051ea20d07947f57b0a69541f9a658b015ba7f4ce095f742c8047f65400f 1669196441000000 1669801241000000 1732873241000000 1827481241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-310 \\xd63a9c8290276a7b63651e6ff7be83dbab9b6e238daa3772591c62896fd027f0ba75322957ba6c009281a03455794481470db2ec51818d14b004564847c70f03 1 0 \\x000000010000000000800003d00b485b466e9315e63a87b82d2a1d19a389bdfa14286d98fcad7825c8f729dfbe5d1d836b3553981675145cf0483641340a5c6a5dc89a3ebc03f5f4db00f5cfe523419f0a8544cb44aabdbf6084771bcb6bcad3c10b02127fde361574e85020b07f1a218594a4d9ba58509a01beba809929a37f09eaee14c98f1df90795d787010001 \\x9b432346ae824b1666322cc5b2d691d3894b84c99f4b183bd0d134b1368cc19ece2e90a6aab30d15ad645dd8729d9b4aa2633111620d1366e9970bfa3797680f 1645620941000000 1646225741000000 1709297741000000 1803905741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-311 \\xda360e77983c34d17dbe51d100d2aad72d611bc64b4b9ed29a0f8587bb7514aad9229cbb93b10d045458a4aa3cec9feabff5f25c3e97dc3ee2d2d76baee57761 1 0 \\x000000010000000000800003c559538863a0f9af2b889fc2b4af037ff21923e85348f8442333ca39269692c5abdd9f5fc0cf935fd5287c72e56605040fba6bff71765165fd44fb6cf28a984ffae3dddcb96f8c8394aaa037e96cceb930f55a73eda8f3f6ec6c010b5e1db92b4d51f4873c0d76296998d9a0197d4d7dc1efec98e94dcc89198ec45986165419010001 \\xcb1b1beded353421080e9efd8a243b8c69b0b86c79d25d879ad39248aa388a70c41b871dddedf2e375e212a9d233cc796e08ec3be37461c8304ee35eee925c09 1645620941000000 1646225741000000 1709297741000000 1803905741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-312 \\xdee2e183114c218a925a406d80128ea622b488605c8f08fd459cb6c2c3bbcc07ee721cf28ec041020482d97c37374fef782abcb0f2d0f55426e9396c3ba5d920 1 0 \\x000000010000000000800003afec977a63a95fffd44fc7b5642b6a2faaf29481d8808f1b15931b47d139bc4cac28592b0f58e44788221afddd445122b750395928ba86967c9cefaa6c26c5131c3e0f11aec10fe372129fe2b6e2b34bcc6ba77330dfd6f8f5345a7e0ad9c2f43bfe2f51f6895267846632b673162dd1af87c74001de0a666df3e068a7840a41010001 \\xa5e4a010c8af6ea08ad23d6b9b8e22c1f86ab1907805f037f5f77965cf50cef71540af241c89b8ea06c4934f1a508468b64c3e513c171714fa16aff8711d7003 1664964941000000 1665569741000000 1728641741000000 1823249741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-313 \\xdfbe1d6efe1591e3add2765de2ce519dfa16341293eaab3c5c7cbd5b242761c186dabdfc6032efae2cfbc35d21ee78a79e60da2d1d7807e54a414c6a513967de 1 0 \\x000000010000000000800003e1b7543f1e750b314767153942c7ae09d0a90f327f51cc0d7d5f900f3fa2128f2ac4cd3f433244052a37d9fc39d1d587e1edefcc7aa8c22fc1e10f3c1276bd98c3a5a04258cc12c751d8d0e49804d9753c004e2e94254eed9f3b3b1f469e23f629cf2c0cc36fff3d1f4f1a020dec25e4ec7ab27720892244e06334167f0d4dbf010001 \\x438b104b8b68b6bff168162b4cbd9a74d69344db2e37e0af58f5276570d4fd652d992e4858200e49abbf4f46617bc2c20e82ae98771ef6a8f7b310f89993f000 1667382941000000 1667987741000000 1731059741000000 1825667741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-314 \\xdf9a5729d5ef85b3c73d9bce01dbf6eebf1ff77bbb8eea0ff04cf1885638507043ea2bfaa32c55475a630592e4eb0065f7737c5ce370e0689dbbd1748076a06a 1 0 \\x000000010000000000800003cda4c4f9a6fec2fb9017d09cbbe85cc75dd35a7e02ebfbab0445a38ec0272ed083a8e34a9f3151cffae77e8f6856b6bd1cb12fbde739273bedde50b2f08dde4177718b26436ab29ae1bec712a97808299565be21d2e9054e9f787edb4bae73007646841c23079d6363e41115d752d85a77e7990ecb6f7027e1e5ed0849d3deef010001 \\x68986aa888e63ced2d3b4ad31895187dd6907f7fddc8b8cd80be279cd23ba800b60404118ba0ca10bf23e944b1db8d39bff6c60f3b0edf5126de0a5f746c7f0a 1658919941000000 1659524741000000 1722596741000000 1817204741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-315 \\xe492c4d62beed3a2ddc69e1aa9ed658238e3b0c4f861ef9d5265ab9173b836f9030d081a3602f157d1d1549ab7843121c873f4c5d622643e8fcccff8aa134c42 1 0 \\x000000010000000000800003bd4cc4823b9429c3e4601ef9d55ceb981d4e06b806a00633200d58386871c30afd6ffdb3087ea3385c52fdea50c44462d8aeb083517836e3ac8cf92445bb7ab730c8f8e25c5293de489364594caf96bf25bf2e3ffd0f85880056f403b7737e65074c8e992c322f7d515e9365092bfafda539a2aab943b1468036725bcc8bd81f010001 \\x07008f5630cc9c53f85e88252e6f7f86a520d93d8eace1ad75dc5cf2ac4d9e9aeb578dda6b610affc7d7fe5fabe1e894cfa4f70dc64ddb52f0281de1c8bd490e 1657710941000000 1658315741000000 1721387741000000 1815995741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-316 \\xe6320db940d50924d60599cd9b23455265674a24f39b697e3dd221a218f5441d1edfaaf48b9f2de528e216a295f6f395647b0cafef12708be34fd89e71f538a2 1 0 \\x000000010000000000800003a3f2bcdc8e09bca1bc86879fc87bcc662d61b53688e87d674a12847cd2f4fe43be42b6958ff0ed1d9dbe7982ccccc577b6a6e19f58d817748b5457f727bf7360e087b42f0cad0be0585bb9619c3fc850e3c512cd950bba77b5796bfefe727398720482b6d9031f3fa48c918bfd4bad53af1b73cfb124893376987ff5a7ba2e17010001 \\xf42e5c0a956568ad43d3860962f24b96957ea6050705c9ce98ba4eaff5f046ee9a0f6df9cf8c0b1d8e68b3f22880252a907cef4d4afce03147e9198c81713408 1640784941000000 1641389741000000 1704461741000000 1799069741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-317 \\xe9e29f0f0adda23db03df7103420ffe4a105de0b0b0a2f7b7db31a3ec7747932860305a24543854aec7aead6666b61dc18add1b74aeaed9a02d57400df42ae51 1 0 \\x000000010000000000800003aac15f248834789fe58b5fee661994e9e054d620da523e332307bbc407fbdeb64c789de2875afb55e9f8585f8f6bdb6eb23dcad2edfd2c8f8a55fa29dd81168fb246e98acb0150ff064b1d7aafdb0f60046d28ce1ed78cfcfce59c27facc52a262350616a81a03ea46b5b28633bd74b7e4660df0612e13721a70f8c75588e7ff010001 \\x617d937a675ff100dd4f6d769b1d229bca566add7b89c4347159057484018615635900707d058954d254a303f23d4b7ed005e48516ccb44504599db72428880b 1641389441000000 1641994241000000 1705066241000000 1799674241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-318 \\xeef6924cfa7bd3fd5177fe34652f34adadf4fda7d8022f95ffe89eb41cc27ced90102160c9e4b2272ba838b41d619b4a3e7c0f9b483be8165a8fbf9bc0c505ab 1 0 \\x000000010000000000800003a99754004fe513d353b8bdb5ecb138e387e7332ea9876a6e46e1266700ef6732d55b696ee9642589c6e4d8fd633c17f54181624b624002379aca13520c9bceb9066b29b244da92c8589dfca7a7d0bac4e83d6abbd51f816d8f769848fa54f53a3228936663c8657c199daf860d9391caeb1969be9afdc390346b25bad46ae50d010001 \\xd234432b22a1ada5ef5ebfd26ed4966b5b893eb20fe728f6943ece2e89157999ba65e3a059917bb5b32b5e95db22eaf779a8258193a152163e98d222800a2f02 1669800941000000 1670405741000000 1733477741000000 1828085741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-319 \\xf04e7b61cd215ac8fb757c7c8b710a619d47ddf4d8288cd7ce67adb779ab5f53494a60b03db9ebbd8ec00f6fde4e7a431fc35223a57220e2eb105760ad445ffd 1 0 \\x000000010000000000800003d48ccfc4311b28248541db1ca1905779a1134baeaf4ba105a31a9aee89b4156a89e9aa8ead53435c2bbd9ff3a798a992d4a0f6e47a512f607ef3bb91eb0a317f11485481ffc395885466d5248f76e0c60f695f6aa352662402dd4fb86059e67277cf70d1c4706f04f76625cacc6590f238c56218554eec04b5f67929d1879b8f010001 \\xfe43a90371eebb87691519cb49c8295f29b0ad14bbf5933d9b0396ede8e56900fb215cdafd37f1c72fd8b289da9380670b0b67d4afb1e6b43caaac5f16ff4505 1645016441000000 1645621241000000 1708693241000000 1803301241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-320 \\xf15e0da399a86b17693133db4c69e0bcc51ba6b6b581e99844cc241d8a429e6c0626446a93d3cd71eecf0274362e3caf395476ee93337872d684755bd74baa85 1 0 \\x000000010000000000800003c9d86387ada7b83559436c18a5703b72d6f9bcc1e3bdc49cf42802469680bf76630275dddf42e3dc0be9fe87025d89f56172568844a93e59686fe4a3b5666a2c21455048dc470b2922b1f3be28bfde53e7a0e894719e899eec5a8cea69b71ccaadacb02ee360b8a5eb623398d67df635f147d08258b3cad7424f75238c1756e9010001 \\xe36db0227b000a88248d576dbd0b36bd02a33fafb72e2c74be5b457c4054886c2d3e28cb428bb36a408d643169c4c8ed7fec3787f0acb1d4509db970a450250f 1658919941000000 1659524741000000 1722596741000000 1817204741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-321 \\xfbfa87cdbe15c178610010921665b02ea8bdec12e29971ab16a91ff8670bf25c5d31f4a42b9dba8a3710c5df4f3b670644834a2d0643aef694171ab2807fa321 1 0 \\x000000010000000000800003d3d3a45bf099c8df77bf19f2f35e46e5b51be1837dde15336b84cbca274648d1a398137997c1327008d07d61fc5d8e10df0e87d1233a838db65520296b6d4693caa9bb358631d2c4f4d931dda1d65241fbdf40470999691e6033fd7e0b0df28726939e4b65203b4bc4f11ae12f55b43ec34a3bd7c95d13d884edf83d3a772fb7010001 \\x1f505d46c91d84e6443368ddfd317d852312c45cba8ea9069b3adab63674fe09920d399be69a48e3d49c300539fca39326875fd034fc1d27346393d28408570c 1640180441000000 1640785241000000 1703857241000000 1798465241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-322 \\xfc1edb4d423704ab3cf7567286dc3c6701bb56ad8f1114a2214daa95799eea882322f75c9162819ad3bad58861775d5c449fe2ce83bc06fa7c6d95a103c3287a 1 0 \\x000000010000000000800003bb73c3f823beb9ad2dd3e76ea445b8e9fdaa1bf183b22d3126efe232fc12c929ea04fde4ee1ca7451481f86c153a319005c1e39966f848f602b971eee791c4982983e68c1529d35cdf68d981d33dd8e1a0f178ff9a9a93c53e3a4943a0b9bbc15f63046b4472adfa75b1e03d99fb7d4bc29265475ad0647fd5e76071619ee4c5010001 \\x2273892d6c36d56e5dc674006b2ac0973a57cd506e038d6bc0992ba046fe77fb9eded80f87b40bc2ca729feab23dfae0adaa949b5d1a002f00111e1d87a4c107 1650456941000000 1651061741000000 1714133741000000 1808741741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-323 \\xfd02344fcb57eb686b352807944bd1add71f90f068e9ec20f83d48b24ffd554c54b2b10dcdc8a2610520b01173b1dbabc2e40ec1d73c7e9b509a555221da147e 1 0 \\x000000010000000000800003f7852d380ca9424cd0e1ff0b031c9d75d94715aaf7e764c444c6c55c6a921950a92ad54b14d6d48cdc097833ad26dee802d79c8180e4400b5dffe545ddc38e0943b35fce248e5513a6e818812c13dc5028054d404442222603c4ce88807ae73b95ceb0194ea3558c6db3d42fac585d46f76ecddf21880dc27bb423814cd2b5a1010001 \\xcdb4ed0c618559099bd723fe47434e83a3fc275d8610ca6db2df0818c1a8ad5e314658b9f35d524bafd698b929b5615dbbd2b807f8ebc3c487c0e5086945aa00 1660128941000000 1660733741000000 1723805741000000 1818413741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-324 \\xfe26c9d41ef170a342634a5eff2e06b22e5ad1d65872a3d80f320d8f96b44cc93678cfcebde089dd9a95aaade7225e9e4e5b301477630ee57db3894da8e178e2 1 0 \\x00000001000000000080000398bcfa0559fdeb0d79ce3b673c2a9ee555de4e2e47cc898dfc6a30f30b6d9b18cff7fb04d0a08ff2c9df6a330f7c38cfa6ac6ce8c21bb31f4906b6131619d9c497ce8940f2d7bd4f117128c2d948dae1f34d7c234d131eedae8255d02f803c375fe4c9cb57095acf4ec12469825b07276ceb8215f9e67364fa0405388a0e461f010001 \\xd6638c0bb00ca8eaf18a6fdc52ef83b1d7b61ad4372b3f051133eb3ba16ce4db38c5b277e54f72cc33f5b34d05bcefaa3881c49eda80019a6081670bedc93104 1641389441000000 1641994241000000 1705066241000000 1799674241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-325 \\x02634c757c044c2eddc51a8255bcaca207ca8e365ebbf10dde04b316287ed180326144eb269f68441338603830d2c7e9dd184e86146e3ff2e87bc9148e33d959 1 0 \\x000000010000000000800003b8780182b69a4aa1190081b29d5790fd4b4e931dd9e9cb957dd0c3902e34a816f33967204559a515fd7fb593c101c61b4b92072e9cdeb579f74643529dfe06580ed3300aff4a8ebad1dea9c5defc0facac2c79996d61e58701f1c30290b6a515fc32ba84b50fb50c674e349c33ed9414c796b0acec40a2fa95cdd4c3c80a07cd010001 \\x6386b09ae670791f9d70ba5dca5c58f1bb8c5950d60d528a343e20072803c0e27c143ca2ad5d8d4d27baca142a8725c6de9fe71edac9d54cbfb1286bc93c0002 1664360441000000 1664965241000000 1728037241000000 1822645241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-326 \\x03afe59f6cfdeaee4d8c0799cd1045c25c815610320adcf1d019970008767cf69a3fee94b0251e740d171edd48940a2dc8d955e5a22cb979f3902673a914c130 1 0 \\x000000010000000000800003e361dbb70f78276759326e96ca86972161278f3c797917d4cd8897a1e9a6aef35ac6cfed437cdc86be860ac4f79b92039f455a9bf76b1e246683c510c0eb2c9c52e79bacf630f045946b1753409c1ea80e9854c8c5fb35bbaf371bee1ce7877e6a944c6ebba57104be01eb1251ead07f61bead97beaa0bef845b1d8d895502b3010001 \\x1ece643e343ec58edbc8c85005552f2f8282b669d25bf6a04100ada566b53559ee39e7b737ea554b9211f8d194f5c4f9806ae502b3315f3f8ea11655bc8efc03 1649852441000000 1650457241000000 1713529241000000 1808137241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-327 \\x068b0768734daccf24b334b1118ca2e369409d3611c76dcb3838301ec1d8bd6fc751593bccabade6cf0e244697589eb307edf2a2fb688a9abe5ee7555cf5414c 1 0 \\x000000010000000000800003aaaa2b5f1683ebafa80dcc457f5c31c42c542bf0a48200268e578c157157aef98e9d2bfcbceda056724278d7495a323a585c61844b5bcb4d4dca3e833591d659bdb207a948d09ff8ff1d1199713bbf1f19a7661fe49d4e4e582974265103204d0e3a284d274a13f73569df8d85340ce5993de228c0e2530e1d74b8fb0d4ee543010001 \\x352dbcb784f3fe82ac2b18ba22636bcde41623664c174683c9bd2b6b51a67866492f06551eaa13a2bca01d708639e5bda97ae1f4355892fc6e4177e898c8fe03 1660733441000000 1661338241000000 1724410241000000 1819018241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-328 \\x14df010ca82b05c60e642827d1ef4e7ce005986a56529c991fd14ae4844cd5a02491acd867c42a57affa31d0d64a3f0dbaa3fa700a14f7602912b5951d479548 1 0 \\x000000010000000000800003ac9817f7ed027364baa3c8469dc5d3a6679bb3a061dbaae3593a169b67a1a65657eb04374fb84a38cb467377142e17b562daef7d43044eb38a6788045539bbc993cf2a9c3f0b82e47cf5d3e93c777250cfff3b1e79586312c03de3243cf21b6f64cc62c97835aa6e389131b8651d243ea7cc8602e0e26e8bb7a86a47b74d5079010001 \\xc27b486a95abf2afb6f8d330b089e1371c6f9d9fa8c0e5a3d5cc13b061dcc572393da94140b0062c0bd39f0b629a13fc36274837788554aee9dce10048c1b50e 1663151441000000 1663756241000000 1726828241000000 1821436241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-329 \\x144f12a1937c726d5d0c9998713ce5797f9f614bccf7fa41be7580b11fab63a19b9053d75d3f9fd1826cc6336e4c1dae5d193b596785f1289f39525f5bdab17e 1 0 \\x000000010000000000800003c391143a895b446ab77cf73ac505f36ab5828db6865380b00fd0aa1680cf18a4e35603474123c153a11beacb40c8110fb171aa79e3a928442c037d185e6f79548651aacbd9e2c208d8dd4c915918b065e7e80e8427071d254af26cf958fb6fb798e84edeede52b6f8bed49b9afe9088a82909fc59347f6914e9c14f4c76637f7010001 \\x4752b16d9733f903a73a7a640c8fa3b8fd7b274d9d024bcffad530ae9a3f3df3984426e45274ff9cb7d3471dbf78b7d74c83b21836e3902cbe488f9e1a570d07 1669800941000000 1670405741000000 1733477741000000 1828085741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-330 \\x149335c1462f4f3406b4e57627826e82dc880739c219cfeab58535bc43065c779142693cd708a722f5b5cd6044f395b85cc039a13c7a6b54da73f8bd313eae5d 1 0 \\x000000010000000000800003b6053e25e600ea998f97059e43b8fc123c51c4b3b640c501368f578aea1a276117fbbd3dacea4a8e5bff332fc1c929e4855dd825f161c06d43d8fbdf6c074c8d7dcfcb81d1a9037ada7fb7f57f064ba12c703eaa8c42662a365b25b9b27dfb5bdc74b49496ce1e1b24e101bb5eba039465da616b28475a3e971a6fd0be1f31d1010001 \\xdd30afaf830c019e0869f756a2106501dda729ab596dd99fcdcded5f4a13b39e1041df49e30bb22aeaab401fa96d8cd8538bbf3f84e4bccd08c60da45a5fd90b 1645620941000000 1646225741000000 1709297741000000 1803905741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-331 \\x16237e40a68e11e1e7460ed19a1136c691901bf6c005da4ab919e4aff9750751e9dc6beca9e668cc0e0b9ddcccba7eb0a75ca88518983d0461b72f93c0d6fdeb 1 0 \\x000000010000000000800003bb9dc6d3a559ca964654619273926f172b44c6a8733d27201a3a66d0307d163d62faa0cbdc173010c30b6bc3cc448c74ad8b055c4aeb467798bb689a7a12e38a219cc89a02ece9ccfac8c751e6be919696f4ed3a65d82329f505681c5e7a7f53d34dfe17823f89a0c44162dced03bda33f6f2632a47b5182756a761fe26ec52f010001 \\x1f26c28ac45eb4ae66e8d7212a9c4f2e75cdc134db52b20180d9842e3ccb143b6eae416ba20fe9dce0e2b3c39b037ec38dbd916b68b97374f8925b1526b28403 1648643441000000 1649248241000000 1712320241000000 1806928241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-332 \\x1b7fc2600295958ada64f9fdb26615b58fdac6bc9cc803568735194216016449db12ced5cc8f3618f6ab230754a1dcdf522a11445c6be7aa50fc1be72f0c57a7 1 0 \\x000000010000000000800003d659a56c0678a586507869c57e74873c010f34e3bcc5e5d749dd011cbe7526fe087c347de7a66a55f83b6428b59f035af753daa5ede4f30e95559e490703a1361418f259a5b3ed0944fc54139418ed362a5f47a8c9937e11aa930286b988b27e9b0a533eaa083e4439dcbf4ef59c5d1da6550e3d71d205370acdf43625928fef010001 \\x2c7e3dfd5a09fa1cbc6b377d7a7e384e1625fc13bf8b5f796a883233ef15b4ba0ca85c1d8b1c41ae4aea46b4f9c54c7caa4bf1a3daf37cf8042baac0977e1000 1667382941000000 1667987741000000 1731059741000000 1825667741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-333 \\x23736a67482554b097816a0248cf2080d64b9bdf5406d0dde6fade7d92074f12122865e4d4d06e19f1dad14ff8b7e5b31d2a8028deaf1ed98d23789c9bf28d01 1 0 \\x000000010000000000800003e608f1d8182b3475731b16043c84b74c9f94650b1a0404d95e1b9cd3720a6d64a6f2f9b6bb1a7dca1d6068d78c94be81fe4cc33694e39fc824f01cb4c4880112eb4667f11ef67e7689ba9d32d04d404d574a54f45c28fceb72ee9c0196fbad1e1c09fe32ecbc831855ebb530014a00efed81785ab6579c80e353bf9a483effdd010001 \\x8c285192a562e074836c604d0afb92493b07c085e4abbb2ce457692a9434319178c171e5ddb1d0d908dcdf37f0a422590b7be26351c9c0d708dcebf3a484b806 1652874941000000 1653479741000000 1716551741000000 1811159741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-334 \\x237b26aae41f966242ce4d0c5f698e0473a0aee000ebf9a245c86c09f84eebc1db0d889e690b39b015409236ab193d90db079bca1ea1d06a1fa8390db9bde175 1 0 \\x000000010000000000800003c2490ea9f44b739f127e9306ec2551aa1b9ad2bb6fa64c2febff378784dfbc42ef7983fa1be71a546e2948d8104cc873363cd80fa7ff79377ab5a4acdf7d22272228b6be11dc3752143f458f84b1c7ee7e93f7d19cbb8c1db2557122e0b7b03db82292d2fc4aa51ece2c74e6bc061a3951811bc5e3f145c735e19dd8d0bbafef010001 \\x76121d85624b15ab38a901958a6418ef97235229bf003fc38f77a1ebe3fd7e09be697c7607070c19e7baf2b617c3d286041012846ffbd0cdff35ef82d21fbc01 1669800941000000 1670405741000000 1733477741000000 1828085741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-335 \\x24a7c763963fd5f8e7ddb970b1c01aef84e8ba9d803993d94ea65bc94ab87908ab110b45d21232fa53a7d8531ce66145519f59f935bf50569be4a5c198911ba5 1 0 \\x000000010000000000800003d990fdb7abb6f47663e985d46b6930f73c73ca3c91e585b4037b8ac5f44e62cac507192f619dcfe55872e6ef6897d82155c4d036919711601e7f958eb107262bbd9d44d854fbfeda10dfd41600bcc1faf85e38eb832f7d74c13c7bc56e9948adb00a86ec0691b736266e3cd33202b5dff65b4a59247c1be257c7fc5a0464fa3d010001 \\xe06fb51e2b851106f0f1a183ab3d186272d7d6ef5d6c9f3c79d07cac86386c8a538d32ff93107d29b5062d449a53d1b728a140fde9847cd2a9ca3fd1a258a407 1655897441000000 1656502241000000 1719574241000000 1814182241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-336 \\x263bbfcc82bb63fa4d31a65093c9d3640bafd3bb14faa817ec6889d8cf3015c0bbf58d186477cf0732a69ae3dfceccb39c39e2234be9a9024cc71d6e91a268da 1 0 \\x000000010000000000800003975a4bc7b4857c5d520dfee321b7406ad5152b5258e80e246be6d3eb1b8f68b8fb61c498dde0bd43649622f42c635f2b663d112f7393236f89eae86636293fa284bc305c0470eb0ee987fae45d3d896a90aaad83a98e417876645da0f263fae753ca0b98caaaee84b0aa0e2b42b5cce4289be4b76fe14dba7fa063bc257bfbbb010001 \\x324b569f27e4b838de72ac6498bd1425aee669b0fd95871fc61d05a38627cd31264554903ec694a15ca0f5ebb4e0c4b1843456a117981215734b6beacc3ffe05 1664964941000000 1665569741000000 1728641741000000 1823249741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-337 \\x26df9a35201275c2227fe0bb04130dd18bd803a2b434529e363848c45e84242ef9e5ab8d67540f32489e7ab1c21ae497a5d5b376eed7d7e94d3a18b027100603 1 0 \\x000000010000000000800003c682f22531f784d721ae137f0c2054483f3e80df090679d69e719aa8f0e971e02d60312783be6a7463e4e75376e07b85f05649b804e548490000e5d118b000a4a7f5c80ba6ccef3eb654e57aafeab50d02104fa3e2d2b91313f3ab82bf56b76bad846e55cf5e7251ba9f39edb835dd2aa886dd64a7a15a63354c955ed9da4d95010001 \\x7d96e58f39c57f19dadc841f4e0500ec32bbabfa4f9c1cbb1ec291044bda33792d89d1ec3052fd5f56f2d0a550b234bf7aa32b3e3e132fe20403a3d17221ec0d 1661337941000000 1661942741000000 1725014741000000 1819622741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-338 \\x2b3f6055b623a0683e2046123f21edf405ee620fed1cdc4393acf0313d1ffa63f0c58006db79bb3827bbdf9634b3d471ec039203db19cd793bdae8fb042ead96 1 0 \\x000000010000000000800003ceb94dbf767cb117477f9123eb426ab4e56680dda0fd846910d384449e1ec9225a5d8970694bc96c25133d9bed7a4808ede582b091c594f2c9624e3dfa73461e36a361f12962832bc3362f7173d74034f01c9d63a04c7743084a24eb0d6cb75038d2dc31cb5588aee37e481fc5f34e2cbc33c63b74f8ae3ec83743c8cb413965010001 \\x6cd351b6a7abcd6cc6404142d52b587516113dc72757f44a580c0cef4d0793a5a5a44d9bf0fdaf03d27298424eafbe377fda385d1dd1d3a37427eca49bbfa30e 1668591941000000 1669196741000000 1732268741000000 1826876741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-339 \\x2cd3ee202c930e9ba3de18504fe5855b510d1795236a5614e25686adde4c9ee87ca6a1decc1347677333bb89f59f2072969011f98da9c5d9466b50ea2db3918d 1 0 \\x000000010000000000800003a058e2f23c094afc4221992c9086f5d8897a6a562a3e3c7987e220a34a4cdfa0a1d5cf2abd36a289505006cb7992d2ab448c439aff9c8accdd17fc0e4d6f7f35ec96a366762ffe150b208ac5154603741c70040840303b46d956ba1a11bf6a4c338c8486ae33197f7a39ea1fac2ad202b4f1056901defb795020953955f829d7010001 \\xcc04d33d631f82eb368fcb228a2d125ba602536a884c67220542a9e1401abf53d2cd5b14c297dc7b371b97769ab1821f8d3127a1f5b09f54e05645601d87680a 1645016441000000 1645621241000000 1708693241000000 1803301241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-340 \\x2d3723912c51df2c415d62dacc7cb7dc77277ab508143b3ce9b786cf58cadd96b4c66941d7fc510840c3b389753c855f6d0c5a5f0bbc672545b5d6a41a50d3fd 1 0 \\x000000010000000000800003d4df5eaded25b9644332dc111c7eeee9b6b7b434cdd873f6da0891f1ef494e7326d87996ccda166b063054f54c7ae6d748cbb41fe10dc99c754f6c41f6d3ef77ec37b14796de48970b35e809648620d6f159407d91280031ac23a70133c0ffd339b7ea56a31a1243a9c89986ef11f5b7e50d33b6ef2424a3c4e6bc6368fdfd99010001 \\xf311d012be5edb50637338b6f8c728f2e1bd11695e0e913f8e88eb7cd97662202b20f7a8ec27844d09f4bade1ab097618e786378902f7ffb351d5cde2b1b610b 1660128941000000 1660733741000000 1723805741000000 1818413741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-341 \\x2d07ede800f34d2213101662a8cb6cd883aa2b0f861dc9b657e270ce25aa741f846fbe4c5e6b9b2c39daa34eccb02b9eb1892359fd373872c7baaf2a12a2bfc2 1 0 \\x000000010000000000800003c8afa0ea6c16d9a993b8b9e29e603f1e6bcb6a8decf1cf77cb176bd89a22edbe23963c3b91c5401481903bc945dfd7e10224bfb514c7c75822cb4479f4c32df2d82c34863aefa4527b0301665da7c670fef4934f60f6c50c5f67748c963df807ed9e130ade5e532ede946a6b2737bc79b7e30dcf8a94d35e800b700085a7b439010001 \\xd09fe4ce067a5d51aa1985fd1e2b497b7a1a0d05327fc0c7b79eae0b4043fc8d9335bcd3769f82e2641138ecd72ee3e4a5a04a59497fc75ba1efb736cc365401 1644411941000000 1645016741000000 1708088741000000 1802696741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-342 \\x2f4725d0668d379c51c6e4c14593cc9cd4dbd25f8a41fed6db751f85801e3a8a7a237bc0ebd080a51196e9d680219273e42a460910dd1c4730e3aaf64c83b236 1 0 \\x000000010000000000800003cf9e6a23c025acd6d5380de4c570a14c2821e19c01025dde2010e02662ad33582fea4c6d9a62d2ca90129c71278a6b207b21c03b674b6917154240e0cfae77758b18cf000b16bcfafd1fccc59fd5a2dd4d667fd8ccd49dd0c472c45c76b987b27e82b4e067457c39a35f2d53d4b889f2e953baab0a08c7ddaa8d8c4ac46bdcc5010001 \\xbe000a0f913c7e1cd236ccdea5be9beede41b4bce1b6639ce2843b96ac87aad84e037080f8386de0b66cbe4794efe290701753aa6c9848684c72153d5fa42502 1643807441000000 1644412241000000 1707484241000000 1802092241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-343 \\x334381efa17153250d54f7c8d06d475fde9a71ab5732f50a2efc8972f44c1dfa4feed39a348a89bb818f5b1b8328efc7b0fb0a8d56f75d5c56b2f71df2ce2fd1 1 0 \\x000000010000000000800003b5c6b6655a331ab098c4eb3433e4ae91a7028686f6b384c0e06dab1e7665b53e66d59b9fa114ce980ce34c2f35ce5aefc2aaf2c716f81325940f9b6b62ba575a0f6a95d85682feeed3f7a74fffe01e54ed10709715f99202d3850cfb43518a9a62f13404d9d593279fab6b61614ce4acc26f81f55d69a3de0219613c9f0ff9d5010001 \\xc8e5659d0b9dab41c6d5e0eec91cef7b0f71f41118841fcc0b403fabe1bbab1289b70d4a223a3684938530b613c8e3ee32a66b0bb931904e02c30de5efe8ad0a 1668591941000000 1669196741000000 1732268741000000 1826876741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-344 \\x3d0f266a66ace4b168d1ee744357d76ca159a874d6fca499e7ed62e3c4ea78193e3a442b0c8e75d170715056f9004191976df27b6e7eb1ed956be4ae360c8306 1 0 \\x000000010000000000800003ba366e9cff79d720cca649b8d2458c460334c5fc9b739a38e1e14d4a1a69e15bdf2303b6549b71eb892e8a65b628ccf748b0bde8bd8eddb8974ce440e96a10d31663090382bffb0883a8c421c2a869e8ba06ae36c1aab260e1840287d06e6a5511fcd5484fb1b664fa0f82891678b61a9d120ee39044f2c88b5c7b473ba3564f010001 \\xe728eefd6c1de759d8743281fbe210be40d03edb04396cf7e2124c67d416d7b12677c3d013898a8a414698a70a25b4d4f994cb384ad7fa289c49b4d4ad7c2a02 1656501941000000 1657106741000000 1720178741000000 1814786741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-345 \\x3f33d863e97c14609476a14039556337e1af5a8291d460c3fcaffb849d019f2c53d90204becc45b53312f99dfed0257cf838e7c9f2965f4a2893e295ba7996e7 1 0 \\x000000010000000000800003ac8b78a2a0b8ce2c19f61008101cdf8bbc01f29896a98791c9442b3370ab61538caf785e98cd7ebe9bbd0e6565378b02fe467a06bda9d89054d61205341e11ba8ead80766581c4855f0742ef4076a481f8f69cf588872e9ff19b55b10d9427f7020ccdf6f20a44c77949565e2f8e119799eb6f5cf0093eb497621ee69ad06467010001 \\xba5f22972237e24731fb02f307634d05df6b08fb8524249b77839748d27987b3b3cb2f987421db680053166f6b27d1dbf8264e5c456ee52025c8b13dc5d7740e 1643807441000000 1644412241000000 1707484241000000 1802092241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-346 \\x4447be49a7d49021562b724fcf34c03af2d89880b0a1bc4b291fb8f0565b6e748ba30bb6dd19d7c8dad997321e10be608fe652068c3ecc2b70bad16ef5b67b0a 1 0 \\x000000010000000000800003da1abb75dc80c42a51ea08dd1bc0f327c606c0f2e5a22ef0d7c8fd4d0e24773bd8e051a019068f495a707d38e34ff3ea81dfdb8dc534edb5d4dab2c653a365de95bfff9221d003a4117542e74d2f854d3c79f42e0356356c90960e73456f7a2537a3a36e2621d8476132d6e031c5f154124c0a990ee48593ed6ebe55125e35d1010001 \\x26befcbf41fdd7f1f4a0d8eededd84dae183b1961be48b26175070cd1762e612d7b273092e1ad543926c19b06eeade17b85486f4937e06e0fb6b7ff3fb26790f 1642598441000000 1643203241000000 1706275241000000 1800883241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-347 \\x4663f53621886c3bef32574dcb1d91a35b185016721eebba5cded5597df575737e2467e829497ccbd230eed4e306203dd9a95aec6434e5e7ae7d6c4baa638d25 1 0 \\x000000010000000000800003ba770225f576ab101a3cb88e41aa24b2b1d374c9a324723ee22c6b1c033cf4fabae3d100a358f3e49e2b1fa158f63e41baf9cb77bc256528991269569f8a076c74c4f41a938988a437c744ce6e511908104afeb4414a8bbc649a1c4f59337f586d1a11bc9eac083cb2537e42d47999714114751c56692840c39ff4e1afdb8c4b010001 \\x9c2a3ba534a085477500ffef082ae131d9d3428c1a4613ce0d8ca0846a27a0023d8f689a08f0a17461cf1cd2d74f762cacc17d49c7d8250b4e9475c94469220b 1655292941000000 1655897741000000 1718969741000000 1813577741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-348 \\x46db010c132649f6aea03a469a475d79c8021681dcb4eb408b7a895d3b2315bcb5cf3f4a73e44c5b0883a9c49ad80d1ae1ebf57188490fdd1d7f8360238e6041 1 0 \\x000000010000000000800003e4e01425d49955453f0d9f2da1a66c97a14ff1863dbdaec5fe7d1bca01c27b45169d6143aeeb61415f323147989e93ce902e9a4c8d89f8bc7c5420babf3909e27dc5c968bbd5ad584bb359da2820d21c858306897bc605dd4af9d4298bf769508c580eda5f97857c61ea7c95bc708b56e9abe0de7d71da918e33bddf425d794f010001 \\x7ad582d7e133a3b89cd7d6a16b47e9ce9e3c3f2e75ceebed98d92d4d73d2fc2b90fd8e62e15ab25df519d83236beb0908c5a1429f5dea7395ee96e1d8afbce02 1641993941000000 1642598741000000 1705670741000000 1800278741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-349 \\x496f877812c39fc5f8a8d0a06ce28c22058d016374f91c5a467e37e5498788cc0019c463f67fa8c683fd504e2d70efb8870e29c4eb7d9b24395dd341c059ea50 1 0 \\x000000010000000000800003c029778db7a148af85e3bb0a1f33210b643a83d6c613affdf10b5daccf82b162229e053c5abd80a392bfdaaeb6c0a6e56d940afb2c0da9c9028e96fd88ac3998ee6aaad6bcbea71345bf853c35a274291d80acc083b6f46e1340b852e595521d7d3e06c87d71fbc19471945bae26f8dc52a237bb8a003f9f22a45582c34b81fd010001 \\x4a65c3cbe10a3615107e22bb316f1968206800997aae60afc7d13544ef500aeeb331991ae5e488f0a79dde871acf4b5cb98e031f739d3f2a00cabd9657ffc107 1646829941000000 1647434741000000 1710506741000000 1805114741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-350 \\x4b775e01c7b89acfee18c3542709d0448a51a890e03e5137a42ea0de638e7baa98c27a5e13869484c93c14bf1a6f94392c1048b3898bf4c7ebafa672ed10e62e 1 0 \\x000000010000000000800003b5e1cf8272d95e136c5a0da7197f3de91137d571b86346b1ddba7a9bbf414781fbf165290bb68859970fb88d171dffa3318558dc3489ff54f792eff52e9a0c03b9e6a858a83278d7b52810bdf014b0f130ff0653ef40a10b7f74999c738ab39889888e5763e9eaf46e980b9c6840a13e363437adda8bc56e11b0383b2e1bcb49010001 \\x121eb56750300635f9ec4ab2e8506aad0f979d7ddb1c2d76f2b798828d3a6a037bd51d8cf0740ff9be56531720734c49793203e7c93be1c2cf23eb58ed7d2900 1648643441000000 1649248241000000 1712320241000000 1806928241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-351 \\x4dc77644a58b16aad3fba48a2f1f46ee91bbc51e4944405b526484d6c79e7a690753a0897006b91289c2993e641eb34881f7dafd03a2b188614a86e76a234a3b 1 0 \\x000000010000000000800003b39cb8569c381ed7a964fdd079a901ca4623fbe07bf2fcc1f8ab7fd1d40b9a99106d08e0fdd5d78d3b7688ec0595b7c1c17554d2cd3f3a2a64eaa1c051f2a6b9c63742bc33e3f3b220f9d14d421beef2ca2eef3b92151ed77dbcfbaa499d0db4dd5a3042d81719b3507d3a518f1afc22b7bcc7e9d3a12875e185c1f4f4fb757b010001 \\x1bb30e7045f26a60e829ca96507bc002f224fcaf2518a6c07d15692d0186af2f972204b0accc15e9665ca718566b5f0601d079fb400508cb062ac63721efa20f 1662546941000000 1663151741000000 1726223741000000 1820831741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-352 \\x5513e2c6835db6c9e899e0f3daa43ecbd6891fc882f046f3fd92a4cc7d9103a8ce2ce12c65e819a542f07d7dcf9ace5433b7e7ada4262f568c5630fa187d4709 1 0 \\x000000010000000000800003aafce84f941df0820b1ba9b53805d8c12cb2c729d85c577572c77edb14321048ae9866fc2a92a1125630b18a2db84677a7fabbbb5128102501820540f24cb1bba7ff9dae7eb0b576dc565bfdbac42382461dcf7b1600faa9d1f438e6591118884c12016e50a92496367b8f665fc0f4bd8ffae7e1431ea4fe2fb45dd71f3ff095010001 \\x53b97e47e0cc097e1fb63c31ca47f57b3cb5434a70edf294575a4cac212b77fab22eb2333259795f64a9be59ca517edf1e5678a1b6cab07af81dd9e58de9cf0e 1641389441000000 1641994241000000 1705066241000000 1799674241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-353 \\x56b7e67dd93f1f7f6ff48ec3fbca8472aeb03079fa213ce372090fb479546a7652df722d4face1cd7ea7d55efe98989332b29eef85bdf205ea1fd9360b08db18 1 0 \\x000000010000000000800003b7339da7943cdc31cb86a865cad5d0825422a23aafebda07554413cdd62f9c77f078a5c49ad4cf418879f48fd9662ba64ffca2f78d8dafc9a77c11161a6117c6bfe5ce89eeb7bb8e6d24a84b3552b27140cd9f73b2b7218bde8b060568e965a603d7344057db1a7654173e999936cf6ae9e54e73d0ae94be3975918668f7131b010001 \\x9ff2520681e32a5041ba7a9c4ce996f2be817427c3530f05d043909a51f103cab55b8e03673d644791461063916401cbaade7c2522f5c5d6f506323d6481a10b 1642598441000000 1643203241000000 1706275241000000 1800883241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-354 \\x5ab304ef0246b393457f8de5cf35a17c0b457b5f91b6dee651ff10f98348139512d9c44e24717fedc409b161107ba6c527e749919e593e0a2834f3464adbeda2 1 0 \\x000000010000000000800003da227299dcd1bfa321f27cc80f302e94865e971aea8e541a70296925e8a4b07572d1e5019b6896c6675205874d7b06a57b78f71d74e636b3125f99d7f3574880c9cc32df61a9cd946bf74c83c0f8d705acb6007b4757805fe612dab2a7be7c85c45eaf1db6aeef298ab994ba4c2ded42ede0b490d69fa069823aa7b215cfe9f3010001 \\x34c41d5e7cb9b4d4ee2fb6efe702661e67f5563b896e6b3eb934c32916a07f9afdd6a0dbd1604eed46becbc2fe231e6c4d6d88abee65263d43320a90b49d6b06 1651665941000000 1652270741000000 1715342741000000 1809950741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-355 \\x5c33a0641f8dcbf8610f85bde211bda34e297626cf0dbd642042af945cbe1450ae2e7ef08e71b07dc3b5b23ad2dbf3f3ad2ee8a3ed72ff08a7dd4971806b4595 1 0 \\x000000010000000000800003a63c86430da4dc8fd8626d7bbab896e64f8c2ea791cccc09296f12dd1e8a8b503448f70a1670eae615bd7139b18f08d433bb6ad14e3e36ef8683b87782dccb7eb08a8054599d5af3da41f96425f6c28235a9fb393699eced782708c18e35d6aa1e6cfe2bf1cc8d6160091fdca0068635756149470ae341c684c375706dee4c13010001 \\x2fd02692caae31177cdfe56029f102d4a1574c2f67c81a94f9b9a727441a64902569c661ab04e7368a3a2f2ca8b9e1902c092e238e183cde571fed67c21ff205 1649852441000000 1650457241000000 1713529241000000 1808137241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-356 \\x5f8bdd17987fb05907dc84538a511122ff3721e9ead4e76808357aec1cf41c4b14d1e9656e8a1fd0b19629ebe473538b76552185004156363f4699a0984f2e89 1 0 \\x000000010000000000800003dce760af7f327ac318621e0cfb3f8154326da982a0b3ca209bd0593320810081c6cb1818c82d115618aa586e319c05ad4c9f2c91bfe1f5cab5474ea0bdbd5d86b17f7298565a1bff3fbccc4d1fda509ad5ad7fe26ffaaa1156304a4eac4598a695763bb42af0ffb8accd74db40d2f98cb5a8cc314d3797f9bf8c81197dd8f967010001 \\xaa07c17b5fe4958b60769b79d9c6a7d27da64f210f9914b2125dbcdabbfa19dd6823cf550ef6ef7edb94a57c27cbade1fe9610f41d5f0cbebcd78ee1af89b20d 1645620941000000 1646225741000000 1709297741000000 1803905741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-357 \\x5fa3ff4d48c44a43bcca3ba8aa0f857f65337e98832c24ac5ca5025a7cb02e263eecb9e74a97d3343cc9723141d655308679103c004944e18b51a9a117275f9f 1 0 \\x000000010000000000800003b2db67f64ad412cb31b8f39fc0ac259cc96bba32aeb4ccbf6e70cd37380c03441539efc5692ce9fb11248ba8f46fb0216c30bc00b6b96bc3cdd9c0f1768163d090423a8417fddbac384f05bb53d52fe663087493b945867a264a62ecef7b82a4481af09dea1d308e5f768b3b0b4f485777c7637ead3d949a8a4ee6c7a04b9013010001 \\xbe212df34620034c9bcf7283305b11b8c77b400bc168a207997e76fcbc9e118037d9c9246583ba52d1f838e46abe5b98f55874ef878287b4d7ed65400a680307 1651061441000000 1651666241000000 1714738241000000 1809346241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-358 \\x61b3e35f06c391b51653e9c90e2df68aa21e8fa74181a96c35774e9400d536aac64347720c33a97edce5c72b68e8d15dc3f965bedd32f96efa636f99d2f19615 1 0 \\x000000010000000000800003cae9abe7aeffebd175b4d44b9aa7116671afe8c2cca3a05de7a4634a8c415c3f32f4637faa9d1130be8ab6f4ed8bcc1d7a0238fc710200ca35a9ff686dad45b97a51a14f2c0f425bb7ca32f74075478273510a7de73a453500c5a4adc2005853f679a74c64b7902a1b4bb03b4bc4685693b2980ea39cf1a5aaa3d25018a17dbb010001 \\x936e44ad96a505edc9de7b190ecbe86aaca702a199674c4efe6976c5577fb7801fc40e76736687438ffed02cbc579c44bc8e7092a17b6ce92f8eee5873c69101 1649852441000000 1650457241000000 1713529241000000 1808137241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-359 \\x687b3959e31a50a0306f75b37c062f33cc612e1d5c5b8a2b0c36aff9bee51bc5e7909595dc393b98a3df70c5db896773d50408c448b54b64a38a1da844707f3f 1 0 \\x000000010000000000800003bf306830773cd0df179572ea0d1e752a8ca1e82ff35e082dff4fca2694e67dd9b178f0fdf5975707722155afcd57de73ea35aaff8f96d332cf344c22f0b4f378ca96adb649b4602ce720e3810e750a9d990178de2fad8f0063411bfa7a05a64ae3c82c61707fada9148033b39b173faf68b4d2227897154e5cbcd4a35c435dbd010001 \\x00a91ca69aed45f4fa0afc4dc73e56613f95f89c2ca3eb4195deb12bc56f5aa37af7c829b22469474472219c6e6993f8ac6cf77efcaf12e642850fc4bb780403 1638366941000000 1638971741000000 1702043741000000 1796651741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-360 \\x6953c1189920ce4f0a04fcb42164fc4b5d8bb1cb21da7edd1d3b53727ddb616ee75d958ba7d0734f107de1ee170aa2a50830fa32871b1fc6f4955102f92964db 1 0 \\x000000010000000000800003dbbb582d1e340e3c391e1d86c071bde48c33fed9aafd5d9aa798b71945ff36923ef24677bbc9ea67169ed61a95c9c09737b592eda19e5bce2908d2a881061154d61de001d92ea1f4128469ef0a8ed97bec42472eececf6dcc6ea3773d34b40d74bae1bb76227ff8b04e550462ea31ccf1dc280b68e78aae1d0e55c2603b8bce1010001 \\x3785357e93cf6610740b13d85c27a50bea854300931081b9c77416c25c842e6e4887c637ec8b316063084834fe6155c8f485126b68516c3bf27905e90cbf930a 1655292941000000 1655897741000000 1718969741000000 1813577741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-361 \\x6ac395f083c28f3ab963d2f43494a14d9515a98f6b9918864d127004d2fccfc0bc027bd1c37550e8f2da6e74fcaa420ce9301cf74072d11d4b5da3f3383f554e 1 0 \\x000000010000000000800003c1c6cc1ace57dcd52c7e33b01eb61d13209dcb8eb9a1a2d4fc5cfef5c7a69a76b14fcdef80a3c1fb3d10c0888a9f1cd92be399401c971d45eb0a2f8107df510b6c9fd0929df872f2dea6ca33bf1b3d5143a2e2f7453e161c8441edb0174d26165f1592394a6fcf60a90128bc7be03ab3f0d2f07eeb071f260460a34cac4c7ac3010001 \\xa9042dc8d96018618ee87955c02aa4269448f9439e96d364bafed2817e6df25cc0463bb3944d1993f477311f2a6cb43873a2c401717dbb2b4d4960eaa876050c 1641389441000000 1641994241000000 1705066241000000 1799674241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-362 \\x6d237fced35e1dd2e8934ccab4e8dd0a1cdac8ad36538d5acdd69f600f9c2bc3cfdcf1e356eae8de19de2332cbffe4103ed49a93cce8241b283a2911d209ebcd 1 0 \\x000000010000000000800003c14ff972c042e6133ec4ad98d47bd0424dfbf7cfade6169ade7e49eb691eb144f644a7e284cd8db997c22be079162a1c988737e5762907384b327b54a8fbdb7c356d9162e6c21c6c8fff85db43a8d5a9758751eeaf03c549b216b5a141cf1822c3446bfa077c36bb6afc50669b8bbc5baca8abaa64193f548b1be1f94670d35b010001 \\x94cf2f249a6afaf99300b2c1ede946a9d9fdff223a6553e96ede355643e13c8cb9a5aa020390e179bb2581c5d05ef18f832a758f08a3f9175cc63e0f6083b60e 1649852441000000 1650457241000000 1713529241000000 1808137241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-363 \\x6ee31625e90066f3e1b6f9c94234be6012ab945792fb8946b0f4fc0bb449097723ff616bc8550da4c3addeb1ff1c37c0b7a12adcd4fddcc4ebd6b44921351d75 1 0 \\x000000010000000000800003af185a0d19217deb0375afff283cfe2576c3e6f5dddbfaf7069740ae3bbf975ae59dfc8fbcf13fc99edff220c991767e1c1e59c862def237daeeb31dcbd6c667d02cec9b2d94e5843690e5fbce1a4ed4db93fb4271204213d3725be2db81ef737f9751d85f32edc01824aaa73b54f9d36aa7afea4a317d6fdc7bae1e6de36499010001 \\x18fe7ff43075918cee818c93107ac06e334ca53b0db4ab289785a27b0d4a0cdc1fee4bae2da306988ebd6f0541372611ae5d6cbc39c348aeaa75ed70e1b0730f 1643202941000000 1643807741000000 1706879741000000 1801487741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-364 \\x6fd7d2197808d6eba21b1998283ebcb0f7d3daad396c7a7b3a7caebb04a935aae889daa1f7c5fdfa59dd5a165483f94f2d0910964fc424315118361cd134c46d 1 0 \\x000000010000000000800003be5be58b6ea8890a7a3e620ebf295864a52daae684489ae13a8d9c62584040cf11c13973a787cea43e53d04cd51a700ebcff6d36bdc39706f7f3f27f98dbd759b174afdb3e37f7bf794b9ec40492e6797a89e9f921d8e015ea5cc4d4edf6db8558d1326b2c90372916cdbf57ac44c9f868cc671657b34277eec9449f0dcd3501010001 \\x15fd01b6515f818294f319cb552ea92f48ef9fdc0c986d9bfff5443c3ee22fdc8e96c9803c0c503f95634fd9a1f1caea84fab326b403c9ce5ce375726365d40a 1638971441000000 1639576241000000 1702648241000000 1797256241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-365 \\x70df11adad6bb56dc31f9a24cc3458fbbf5fe03ac3000b9d6e894e36a2f1600b7446ed4035605b71e9fc056830f06e135884c0b21323ca81694e3bcefd343afd 1 0 \\x000000010000000000800003a2a12e2244d1708dfa901eca5dd83c70133f9f4de143a4fa45b042b7c853c22b259b5f7e2da801151337dab395a00e66e42140be1d31ea329249922eefddbec9e70b8ee75a66a0bb8a19ec583b6784517623cddcd9d8f6ec3435dd63be6bf2c8b79c515fe468be13e092de6adb72aa8b735747cc2fdf5131ca8d76dcec56ac75010001 \\xbf6aed2776a31fe5972b6610fddd5bfaa5c5f8534b0262805c31f1cbb63dac0bc513829554803599e05ba8060a007eaad9cd26fc523c62c863aa29edf60a5a02 1663755941000000 1664360741000000 1727432741000000 1822040741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-366 \\x72572b344cc7832223357033428be5debd0ee6213447ea8a7b6192a10dc70d089340fd27c354d420967a59ee53e64e38f106b8b18c75cb350d643631bf12c094 1 0 \\x000000010000000000800003ccaf52ec4918ac3461f4f16940e1bcc0ba49e7fd0443ddc6346c063e1c8c345640271d7355bd695685dfc68dc8fd445452bc5ddbe85ac84f13579aa502b394221f4a814ad4f22459b105c537b768ecd1c428583e8ce8624783af11bf70fa6fccb09ee331cb7b02f403a907715beb7533ff81c5352d6dd1888a49764e51438ca7010001 \\xc30f2430ad05ec7195bd83a5ee7a49243b82644f7ac55a0795440ccf6b50ba751d72a7b122653ca1409771f31934bdfac047665b97410213910db8965822a80e 1666173941000000 1666778741000000 1729850741000000 1824458741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-367 \\x73a7c79c009ad5e05a1711b861d9cc9f8a41e356d13c039243f1d8c588bc33640b1c93ae49d12af38667449f03538d103ec13ba16b087a51a9798e936c4d1266 1 0 \\x000000010000000000800003c5d22cb511e921e51eeb842a9d45e64c6cf293ee121d12f538ebddfdf387036b6cc7cc0570c9451572eff5582a3fe52e303d4fac16471b40147add325cef38ac09506e130a1b626378849c602f39ded459963e12d9bc71dec31b0ca9dd25e796b7b6783a574fb4c347bff914be3973a08c7017b578d06ba665a66b870769295d010001 \\x72688957336e5be5cfd2b3b82303e15d1aaf77603d183ee3f1f9bfeffc19b076825f640d9c7024465c3b66792925c97284d47685d49dcf90c425d9f9087be807 1644411941000000 1645016741000000 1708088741000000 1802696741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-368 \\x7e9bac3cf5042ed82ea8428df4a02df921ba4b8ad5b06609431723c431526f01f65103556caeca50c90918079782dc12d69c672e7a9891ca438993c15067eb8d 1 0 \\x000000010000000000800003cdd86e933dbe23b195743e1831fdf2a2100a462e67b28dfb9a6969f4cdd9e43f431705388bd6df649d53f390c576144843174687788c8f785263edac0c6cfb9c4e10d8891ec3ce7d015968a8fcef448e06536329132c692b2b19e74c6059eb7d3819b61c1f330525fdbaffe38d2d40a7ab5240743912f3de7305c41341ad4b8f010001 \\x1be590d79a0702f579464b14377c87012fb2152a9e9db5a067d4c9d710a03873c8bc1cb5d54607f4337c6eb51d555940ce1be20d87f816bce2b4e9383c847202 1641389441000000 1641994241000000 1705066241000000 1799674241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-369 \\x7ff73716fb203a7b7697e0fbd967a31ac1df5d69f41d0e509aa27aea7a2dceb03480ec04f6b7a8757bf163b6ebcd866d22f21cc50ee30759e23b3d7f90c17b3c 1 0 \\x000000010000000000800003bb3b0c252745446e4fac40a18bd8d7a70ad218fc1621ec4de9dc427b2fbf8ad61359b5404da9d68190d4b98c771585e3d64f528d26a86841274e2a4319e366f537427987db677e5558e05b908595dec70c6ecd4ec35d26897f768baac7b38189c335abaf243a117bea583b43d9b9c991575a498a09beba32365e5cae4b6c9d2b010001 \\x375701cfa37d2cd2a20b22725e0cf08ef7b429025d6096bc053bcad71fb5db5882de0fcc1d72e42dd2a27803dddf39a51a61699e3e9cb9b3a881d23bae9be00a 1639575941000000 1640180741000000 1703252741000000 1797860741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-370 \\x866fb0d4d35b758a5039aa08be3541aa436dfb2ad7a1149ec421411dbb44806d794abf36772993a00fd42e01da24a2d716e929be143664cc2aeaeaabdeefe8f2 1 0 \\x000000010000000000800003a59d1fe6a9b4281b136852980e332146fe0c4b438467f857fa97e6a2c78a4170fb36ba0cff4989b88bf5062aa75504ef08b059ba190e2279481ccc2ee23580b785535007b3b390d42bad966b7bd09f27b7b05f613c5f79b34f0cb4e5756c6412d7ce3069007f911ef584725e150e10f5b82fbacba2773d1da5c66afcdc31148d010001 \\xf365d3b9bb099442d95d4ba6ce59ea10ede69c96583391383b91fcdd4c67154ba1519fea451489dcb4abf431755905e8c963223dc6e2264654c3e69f4258bb09 1653479441000000 1654084241000000 1717156241000000 1811764241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-371 \\x867787ca2b0f6bf658513489e98266e4c161a4f59198365d612801f2feb962c5b34219025d94603518d4953ec99b03401341e661e85d541f2aac08c5afb54f68 1 0 \\x000000010000000000800003ce847030f8d8a45738f665f7595005e47ed8d9d46f56628580d7136c3d4858a23828612edc9946a574f0ea20247d8ba12f99da096547fa7d4a08440d17eff071b69227ce4170272160cc3083c4fe409dfdab0fa4bfc405edd7cf85e43210aaf1e98deae0264d65e7bd7945ce2bad8f7ab49007ae18c8036219ab207838fe031d010001 \\x4be9105f3deb47d3c584ec2fcc702a2eea07d98913eb5da67adf158cbaa4fe232b35e97b41d523723391e87aba800e121c05bfb5e0ab61649a20ec50a2914200 1667987441000000 1668592241000000 1731664241000000 1826272241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-372 \\x88074bfb299d9f2938acf2380b3cf0e22fbc0f455ccf3b66daedd9b84b71ea6d2a5272887a60334ab68b8ccf2c3db2a9a1f22d14b127455c2d5bd266af90dad2 1 0 \\x000000010000000000800003da8883d2ce0c09023855d9832660d46bb8f2b88f0fb567aa1ca15ab44204a68de529a0d7c14d15fd18efb6c358cb989aff08ee473d261115d975360aca3f4642ae977e2aab393569fd5fdf11e6e0090d67ba5bb8c04a8c9bd6e472b55e49ba849141b469f802333c2676bd556edeccde1c4a3582b6026a87e1817a4561a79907010001 \\x976b9c67d7c20d47dd237a4179682985c591f8f5d18115070880a876c1fab25933a68aac16bd1dc5af71fc7c44dc387e16f3ec9bb2842483c3cca62225319101 1662546941000000 1663151741000000 1726223741000000 1820831741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-373 \\x8933b2f8be4ef866568dfddef6cba7c17b382d93606476f30fcd8204a57dbdb3705e67fe5044220d157df523a82abda954a9a20c734b4c9eab96b69960eae89b 1 0 \\x000000010000000000800003b418e252ca3822299f468139bf04eac48cfc2281dcd03e063e9b0c994d95a99cba1ccdbaf186c95694f2335ab77cda5916d8fa60e8428bce7f9cc755eae5c1e478e86b9e794fddce9cf0515f487b45d806f4a5f86a3d36763361a6c2baed06440b552460e1b8cb6a5e4770d9c9fc65a7b5e3cf20f26c021d059a044ca5da78d1010001 \\xfba1d51f4fa46b3abac7e6f54c604684ced70662771f658bcbea5be5f42ed1793d3986796e5c5cc9d43ede468f20d55024b0f81f27922cded32b2257b9886106 1659524441000000 1660129241000000 1723201241000000 1817809241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-374 \\x8e9b9a51a24b24e8fd372786fa224e510a2b9dc09923e503c2e0646d9fa45cddb85b9bb8a461a25ce07870df78a4e297f7e545258b1547690b25c4bf971612e3 1 0 \\x000000010000000000800003dca267a736ec8fe897225989b927e2a1b34bd9cd10da278a44b76c5850bb13b29d72cf153faa8e839ac4df9d346e32d9e4ad01afbd165e93f73a249f6fa8ff04efe47e38cc2769e28d8d26aaadde550996f2a52279b182378ca77dabaa591006266d19a43c41c8206305587681954d93ad351439d8e747937c7a127e74640617010001 \\x200845d9352b7ff267ce80159dfc1944e06e32698df2d3fb9d35a16acda772b2d724e19f9353a62ebbb48d88c4fde0cad3562720fb27895a55bfd8d36cac6407 1646829941000000 1647434741000000 1710506741000000 1805114741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-375 \\x90cf47b32fc8dae46e400a60b575e6f200f4d5c9f2cdbf87ed112dda0034aa791d2a1213593999dfea40d9926ffedb9f4d07ae8f38ab4b2bdbf2c51c9faded07 1 0 \\x000000010000000000800003cb908d3c4945b68bb74fff8f1beefadbef78f8da55dd9b45eae345eb65bc82b1b89a142f435d79a68945bf5a64437b7459e12c6a443f851beddfd0c44bdd69c63a5073a27b8007b75feb212a90e780eb75c3c5b532f4ebf87a91b4cb3a6602f12a54312b26ba326c3c04fdf9c7b3dfcb9cad808437ac9c10526e3c91d8d5d949010001 \\x393912d2d293cf00867bbede0fedcfcc01c30707b80aaef1a29483123f8933b18f42f02e5e6f9e6bea2eebfaae4a55ddb1f3d62d61f47f2c1025c33fdc20f705 1653479441000000 1654084241000000 1717156241000000 1811764241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-376 \\x90673cb7e88acce21e08a342b01e6c19fedebbaf0f15c9986e6fa0c494fca630b45576fb3c0611de60c3b236e4b74a7a923b91181deebd41c178e0fbfafc00fb 1 0 \\x000000010000000000800003b4e9cc1afa463828aa32a7119f926c7475a21f57fc09a17decd233c766af80c3952f0129f649aa9be46ec9c31a05158e2c4d5abd207da4178e867634b135b96ca7619188ba0947765205b2cc76f133279f5198c09e6a64659aa2195309c3ef6f6e4281b9b7dfa7429c86e1daba7b00174155231545f305419816c698cf296c7f010001 \\x4d8240fb1af92e3ce457ce22ccec47e711010d845a39c852252da6f6847f0c1c47e80a8bc14321b7dcac634e553f578018415b53833dbd4a768fed027ee2c902 1669800941000000 1670405741000000 1733477741000000 1828085741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-377 \\x928f7f4a36cb088819e09edaba8b5816568bf50615cc1e6fa5688783586bcfe6534a4805e0da555fe570e32e82120b138087de370fa11db79e5c7637d8c6a785 1 0 \\x000000010000000000800003b96d0e84a3a4df7644421da988c306ea1592930e8b02da205f7be5cdf11bf6418cc79c759e7bb915af45f20908d9f0aaeb5ac44ee046c8a494d7594cd96b49003901b820e3688d7ff48bf6f2f1f0fb4ee248ab83280b5551fc4e30196a7b4ef2673b45ead277ebaecdc6a55561e64086277b2e1022c24be93e027c9ec0acba1b010001 \\xa1e466a033da2cd4ea8d0c4d141956f8250dfe7e8d87db89c189ea1f9d7e51d68e0387f9529853d10693a5ed9aaa10eebbe02f384be4ceda84df3a46199d500b 1650456941000000 1651061741000000 1714133741000000 1808741741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-378 \\x922bc419062265748bd4460fa55f6d6e633d2a1f1a8678da15d30d5845e5cc7a50f7f77434bed32b61c4acc910c50f206d2f75f08c5fa6d33b2d48f0086f88f7 1 0 \\x000000010000000000800003cc40c2a3fcb1bfebc6794035171a773bc0e76755d1df949cc3bb0e198e0394a712481ac10c95afc59e1f76beb88d775f06872a4ce5e380d127519c2d97f3761f5641d138f21f3b89a477ac7c12bb404cf1135f88b657b9f4f3a6e741c37c0f1788aea368c79e3d7137d9eabb170e420a372960ddefaf787293f458c4446f304f010001 \\x6440d765f55267e594da37fff9a04bd08fea94ccf653f908e6a05dd1a101f1cb2e02ce8408d71e24ed3181a40ed54a2ca8c265339bed6bb335e0e5f630473a09 1645016441000000 1645621241000000 1708693241000000 1803301241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-379 \\x9a2b04a85db809844b539c3423e2ecf82d08c84ddb6e95214d6f5c5c38c27077695c231e03df785f0d082386a9f1cf4add9437d6845ec8ebbd91683bb8d8f4bb 1 0 \\x000000010000000000800003c7f91f0bed90e5eb6d758c1dd6e41a26bc9ce4947487c76974e4af11556dd8c7435e9414e0b43115e253b36fa5a1e4376072e2d04fede63bc9b13ba370afeb6d1db27e80924b76ed231a288daf937bbfffabb0fd6e70c6270d7243696054d92373193df02d7b7b6b42375c41cda506c4df3f53c36d35dc6801143fd5b75a471d010001 \\x7adb3a6e813495059e7fa14622dafd79bf07076a9b7c827c424b7c9abdb1c3507b38832fdf82c1dcbaa69dd75739083d3c56add0a443ea2d7b7a0a061be2f904 1643202941000000 1643807741000000 1706879741000000 1801487741000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-380 \\x9c2bd52f90d9256afa34ada132235c2add2ebf64304e741f8b502fc474ccd9afd86fa298ebebee1f72c3e2df7697e4a6dbfe3d3ed651a677b47eb461d8fd4d28 1 0 \\x000000010000000000800003c0f6a98a737932e9600eec5e51dcab9d003b03ee4f0d8d29b6bb7ae00029d10e0eb4047128f2a200f81701eb783c586e6637c7657bd94019732063e3b3c160c813d1587ce0cceecea7560a5e3dd9c2b8f11c7722d2c38cc263d70df9f957bfc87223a6663397192a51a12cad1f86e32bac965e1e9dd3dd720362c476daf20a6f010001 \\xcee37395bbdbdc8e5e130c430bf01eb65646859f468a33d8ddf92af60555ba5759fe2982cb8028cf49722ce9d8a9b80b08e2655e5dd2fa2fc0a92f1f6a1e2902 1645620941000000 1646225741000000 1709297741000000 1803905741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-381 \\x9e67d622b2038a99a594c6fadc20d012e251dfc2a2606f87e066d51a62e2d90d65a1b604a2f5281a00be7c5c5822e6507d90026a9a625e6b44efa12320031f0e 1 0 \\x000000010000000000800003e0df25c090382dc5c2e4eae77ecd843955f713e9e87391ad3dad3e7030e32c16d4105c96600068cb1606b908f33de14be8ed0555293943ae8223daee0db5edb43515e006f0214403dc34f783266f22d5d85a20b9a4976870fea329d9ecac0c89f8362820885bb648a42aee450c3b6cc4879d341f2c7a67b7b720ade6f4ebed45010001 \\x0c7899f6eca1234ed1e11bd96be431990f0ca23656d80845011a4e54e6c964e6934c51032302d8c6a7f3ccce86818b71519b0a7677e2fe8a6bb04e1d3817a805 1647434441000000 1648039241000000 1711111241000000 1805719241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-382 \\x9f43a6ef2f7a0b1f459a065ec6de46ae65ff22355097b3a3a1a26b96a1177fa8d97fbb08648a2166c0f7cb1b64d0d69f4832903592e5766e476a5b0966273a62 1 0 \\x000000010000000000800003bc61a25ea7dbe4e3c3452b177095d1beb43ece601ef5c43de2cd647afc45d32fdc8b2821417fe4ce4b3e24d5ccf180135d4802fb254e4420579842f609a801544685496aa3ca3a3e62b3223ad402ba7f5440bde1c86ba96c2e1884fe1807a6c14bf0ba08fbed3e726be8c58f4eeb2bd70dc7c135fa8300b1cb44a67b4aad5db9010001 \\xb3a4ad2b23ea32d9960cb3ce818965bb3806d295cc9c03f9a49ce8300701561c3af6eb981bbc64a6686bd17d02d1aad79aa9bdcabe623faab330e5b83e80cb01 1646225441000000 1646830241000000 1709902241000000 1804510241000000 8 0 0 5000000 0 2000000 0 3000000 0 4000000
-383 \\xa0c70aecfad1c78ecd38c66537fd77495d8324b8d007b215c5a4724f05f78f3af9fe066c3b3eec8045d78136a5e9836db741b0e113a76e1a98c08d7b2c8d1ed2 1 0 \\x000000010000000000800003cbb04913be2d614e523976be0984ab64a1fad6f0a0804247e23db97e43309cc93bacadb8d27cbaaaf0a8e426fe0e3c76376388189d5713049908c08d8f19ec6b8f7d0e8232deed75b2e6d6be99f216e3056c2cb5a4fb6e82e1b82651d17b67385bc11c090eba2f911ae692d58272410fb95538a83bc791caf8cb91ed6aa18133010001 \\x2ed6e4525928c9f2c55e6b4e2d5217ab5a0a5a402eb211a9f0b847f42f34479afb647403af406067d125f13ef54d0b8eecd394698e09fe1cfdcee16d46cdcd04 1669196441000000 1669801241000000 1732873241000000 1827481241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-384 \\xa163655c0ea07c740a3f11d97ed4da8b212444e6da839172687fc3d2b407e8e0d730fc2accc801d557c9673654c37c986bcc923f2e1c2855e744ef48ddb29f5d 1 0 \\x000000010000000000800003f27beded017424aa728e6512816482ac3dd869e3b7fb2757e81beaf472dfb11466545f1a09f63a373149bad356f265e40f398477e98cbd6c4bffdbd43f13e2a0595585616b9caaf2d3b6adff028678d31b5e9256271e284ab33648aa2fe6ad0a137d0349817739b316c4c8d14d9359cdcc08fd0c7e6de90c6bc33693d556cb29010001 \\xba58d6421c5b23ca2ed1ba06dac8bceb95bd0412a9ecd46e6c9c5787a4cee0a50a7f5e27fc459326d6786ee94e4cba0da678a6d7f01192e9c99a8a8639370a0e 1660128941000000 1660733741000000 1723805741000000 1818413741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-385 \\xa357764af4e739a603e4eff5420086b0740d2cc4fde2f2d309f0750d5ac1ece589bc75fac70267db2795e68aeb375a9baa95d401e6ef6b82f3e246ea6952c438 1 0 \\x000000010000000000800003b176574c5997947a28a00e04dbab6f2f951420c52e2b54bb2baa86e22852bfaa90d57bd20e85cf4f870d88b779124a064282ec5945bb873ca2668a186acc07e22c924bc9448f57ff52a810cb3e0718780122738173f57d00c28b05956bf5bc1c4c16f6f45825de6b786a9d258b42ae729285c88082c9bc8ba790fced55f51057010001 \\xafbc4262654bdc8d324d0bf6672768fbb4cbe66c13dbc477ba9d26618aaeea2c84de90186ee7121fa94cab5bbe3a26dc6441bbdf106dbc16ab1203c04c3b8b0a 1649247941000000 1649852741000000 1712924741000000 1807532741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-386 \\xa35bfedd5065d82c9882591922e53e6266a11b2d35ed9a18b9294a331d3a0364bc406223d678049759493b98ba7ab6f0dfbaed4de26668340db2dd45251ddffe 1 0 \\x000000010000000000800003ec79dad8c1d134029a3c279744e57eb3f77780359aca235d54ff9d20914c93065b80ef7924700b5d3fe7b53389226f3e2a526f4f81b5b8b2ccf9792566941493afa7da1237769f9f2245faed7ebb9380f73c7f2718bf58c131085fc73bcaba6157d7f74e3b5d9e85ccfdf1a6f95a65e42be87595338d7952de0bd0b1898c24cb010001 \\xc2e292d5be82dba23e9953b20de760e85c49f9ed8c61ef5d8bbfb8b957b7e771aa3830537fc1958e20757adb7d961f1473e24588eb777f25c06b2c2eca29860f 1652874941000000 1653479741000000 1716551741000000 1811159741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-387 \\xa6d39a0670873a9e78405c70bff6832b44806a844f9040a7aa3c0a6516b3ebbe11ccd32ac6ac517489bba301716327a17695bdc3f7dc9d51b518da3fe4f37cbf 1 0 \\x000000010000000000800003f130be841ab51bd542cc2838a01301df25e68cbaa0a3a84ef1b1b44d895322940643836085a139dfe186ad8de6d674c299e9cd04313099214e02674dd17b37fb117972078c6e6a1b189bfbfcd85064781d9e616d01d61349cbcfdd2067b5589ca7c97281c3d80988083b5ae00c4a5e8290170c3fd5665bd4a34d79d5dd6b73c7010001 \\x6fe20022d3fbfba0e279db8c78498adf25d969b4ac2807f476571889e75123e53186a5464738eeeddc72e63dfd9b65a4545dcf3628cd9002bcfbf12c8587670f 1664964941000000 1665569741000000 1728641741000000 1823249741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-388 \\xa763d3e25616a7a96629c0da34e3832d0d694edba6c4f6b1895ef7b7144385f70b552acae3329c9d3a498aa999777a0e6dd43b8904103202577200d0d56311a7 1 0 \\x000000010000000000800003a84f1b3994a5724462864df7266017e3b111f9c474b5bd0ac8817591c7440807b558493673711eebe9729a12fe04e5dd71953585af6a019c7e505c8720f5a88e99b643b49ee993740b585f29d13d74960cce99d70bc9955353099cd3d8abec12c036c4c998ad6b2b40283b77bdefb62a9dd8b6e4ba3f196d5d140c71b8ba4f2d010001 \\x1e339181d7eff23b4ea976c298e2f5194246a82e16474abcf66aa4b565982e8bb295b505091482337b463c6f3a79b288c7e8f561aafee88b26358854f1b06c01 1658919941000000 1659524741000000 1722596741000000 1817204741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-389 \\xae2740addbecd8d6fe0e93be711c8c920f6d8d5bd6306284d52ee3c431e213f8d4dc19744b236c0275eb87c45128aee917dfd16ecbf30659873b846675b4488a 1 0 \\x000000010000000000800003b0abd73cf2474b54d423552e3f62261c9e1cd224f0b8744677c908dc4cf353ce31ce725b62bf777ca49b40fad4673ba0ccd77e73c4ad4128e35b8670a0c43d5a17af27732ca4ee91e691b4b4c0754cf34e0928352a9a8dc48e3fb98a541f7feff0352f548c5dac371a3ba335e050d395fe4456cf13438180163b418549928033010001 \\x1df506112f761b22358d3b3a33bae54df9707c4db728eb61b78f6b08790f08fd46e27eea94299921e7fe046aed143ee1c519b1570cc90930b8de1a91e7c4e106 1642598441000000 1643203241000000 1706275241000000 1800883241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-390 \\xb42b894f73c444729cb10346a0c2150fd0fa7896e8654ae4647e6d889d0d3f3568ab7b5606a7e550ee87c08c5c66c4836af8c39076f56381ef78c1be3209247f 1 0 \\x000000010000000000800003c54a0eed9991b5137c70ce89d2e83a154459807ce86567653ab025afa1fd6913d4a6e0f204a87702bc1d0e8a779efe08389f5d958235229d0e8e268b0c8dcffd994bd3af1a03e57ce4848fcebb57bdf6b79ff8b9adf48425c0c4d0851dbbff92f374322f504ca7adff19f65f72305fb59c7a1ab4a9b9c24fdf15d905a7a09943010001 \\xeec55734d90e2445aee82e4fd588ccf7df9379f71216f9ba9c440873e206b5eaf0458cea6fcc02ebdaad2937db68c5d8af875e031afa27891c9c62b88e08f501 1652874941000000 1653479741000000 1716551741000000 1811159741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-391 \\xb84f329af187ff93cae002caf41c44b4507faf144373a6a6810faa606fe9f4457f2f7b50bacc3f0b3946b02dfe5c879efc47ac7b0ca14f218d996dbb91fbf51c 1 0 \\x000000010000000000800003be1525ecd6049c92bd88a280cc5a69363c57b7ecb4d3e309b7d3b5ffed2190a0af0114b57b7b35d6b5db1635b61f72095bac17def7960e515e5039c250c25f3f853e16defb22dd175a565b597d000381ddd1ac9f70ef6cbbbafc48f66f54c21c2c8c0d28f755dcd4f07ed9d58e6fc114c1c618f46b905c152d63a082834f0427010001 \\x760f409b997eeb1adeaef83e68bac8be7a403e6f7da3a02cd30e5c82bc929635c730742f17f111a1455c6c95d5e159b7c59248e49d41b0c6089fcaa98af93500 1668591941000000 1669196741000000 1732268741000000 1826876741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-392 \\xb86399aac9118c85b0aa6e9ce93e3571f7d52f606bb28df0062213c250940dcd9f6205a14bbbf17017c49c001a17b2f97ab59e9ec1c25a738a1366e6d5ca3699 1 0 \\x000000010000000000800003c7bed422d205752655b0c5ea15262d5f59142f2e51a92cc984b81047842bd6c615ae7ee25cc237b628d51f11c450ee0e88ba48902139e8a39ec0626a92b0cb152c5f5ed860fc9c43dbebe868d7ecca65f63522f447e70cfe8fd7db840be1d958542ac6ef1410a29db227f180612109cd69b73957639cdaf0e601e81537d898af010001 \\x455cc6df7f0b1783bc92a641a6753c845c6957d90573cd9d751f309afca363e34c6f17afa51491e595cfa261982736a9be5751773c5fb08862b396676ebe5c00 1642598441000000 1643203241000000 1706275241000000 1800883241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-393 \\xbd6f0064f33573678f779dfb47108675e2ef50a94bc70feedfeeb7c2fc240cfaf10dcdcd019db47febe0034a6954b8d7093d4a0ab289c9cd7adbaf7130b45426 1 0 \\x000000010000000000800003c927bc2a3d2e260ec5cc5ab3b2efd5ec98f3b78b4b5287485c20a5ffa0900e76a2690f6be99af9991f02139dd1f064795a6a54920baa7c2bc05371badb1d2f8f792fdf61ef6a56e5c14492b33754a7d1a43b861635fefcbd4d6f8030cdeab32f772f0bfae0ca31dda2e08c0a40f4b4080a86ae424fda3056bbb215b458aabb3d010001 \\xeef3203afe990657dfe8938dbd867f211fd28c995a57d10a6bd3689c28873205b8417ff5ef425cf0d75276fe3dd3e665de6873ecc89430443246a5870d752809 1651665941000000 1652270741000000 1715342741000000 1809950741000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-394 \\xbebbd0416fd5afdf901231d06a595a77cc4479be7181ae05e727e98b7d03b65c47c7943773ef2d60b3898f85402a98b3191273660a30c001e7dd7c6d2582ac75 1 0 \\x000000010000000000800003d68dc811ad19d425cf45fc7e4941f71a6c7261cc296fd4dd2d8df87b70c969c01ee50feca0aee393e58c06425a898be64cbe08204f90a4d98612588024e9705c40cc193f99bf893df2df60959650a6c1e094fb1e954c773f42c7f2c264301af19b0909aeabc27b5e51d9bcdbaf9188be283841caf23a56942558adead3ae9d49010001 \\x622f490dec2c54d30b80b7fc19b5689ed8182c78516c7166a264d4fc277485b14af48a401647d68f3e48e4e26ec04a9f96d597a62b44f3b9918ec44a5308070b 1664360441000000 1664965241000000 1728037241000000 1822645241000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-395 \\xc1cf078f282a2df3232f41cba057a639726126b9730e6d6a5c27abb6164ba59ae75dfbd3472b7adf3ba55cd7d738d10d65ffdba0622c1c568a8a4fa078e5d774 1 0 \\x0000000100000000008000039a4c0bf6085edb8acf96478672a90f3a03d6b89fe161eb58c37405517160e3e32ebf44ca833c8713cb26582a208ab2bb721f0b6972015837c931e191b332a58ca6d0c5de7d0285dbb5057a9a1129cec02950630d48d959aff76b0394345857a6ef772db1dc154366f79bacf17e1c99fc7db052ef26c645de6643237dcc177353010001 \\x3ced37d5e4fa6fac84d6b1e58b811f34517a122d7d5d53481f2da42253cdc1088aa28dd80e29f5fd4504d0a1c198f2f85bb900d7b07cc57bae8e5be8560c100e 1652270441000000 1652875241000000 1715947241000000 1810555241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-396 \\xc1c765976431c318f33b4ed27eaeface2034118b0605a03d84078ca08494935f9bb28b7b037ee51e9dc4660768a167a5affbdec912ef2a5372fd8ce076e0ec76 1 0 \\x000000010000000000800003c6e4d742caf3d73505cd671b15b9f10a650f98878de2f81aacf8f68b6f195e231380d21f30c70dfa7316bfb375beb5a60a9f0755bb98dcefdcd6e9e58d8c816c93b8f17721872669d59aab7dde964c02830d1b6999bb85f02f2aeead10aff26dabaa1077772d0e97250a0a04014ad943842c694e368d1fb213e71c203222ab39010001 \\xc864f306bc3336cfa3311e956fc9b41ba8a4a43a45c622665dbee69aa4e5d57e203bbc87c868c376c32559b85194867b523009a819275ddbe1555ee888ae6305 1657710941000000 1658315741000000 1721387741000000 1815995741000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-397 \\xc367e72f237022de14ce361230b8a595e75484885a58a35c986be353e638d9dfcd2945a3b802f473c0c56dc4d3ecea59a04e21a7664f88c8880508482055ed6f 1 0 \\x000000010000000000800003b3e47ccc05010076179d458f22b79182c193690bb7d52c6b7cf27fae67c515d3ddc9e621ed72a28b32383eca28b46dcd7ce71329b8c93ce4d9d12e9a4aca67fc758d61136cad571b31e4f9698646e52690a6468b5b804b60e96dfd9bc11e515827dd0539c5ac019549ec4f1db3d0582fef576b6df8eb1ce40e1a785870ef17a5010001 \\xfbbc25b6385906ce135cb001f523f76f76e16b8ef0913b5d7cd014e11285b84c5a9657d35cb79592ab93023854f4c9861ba950394a2fcf7d568f331acad5fa09 1667987441000000 1668592241000000 1731664241000000 1826272241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-398 \\xc92321b97e1cf48f5a3a186a8fb771013bd1da27c3e82d5fd156fef3753fc5990f17273c43f226fcdea8d273470a533237a713f0e0844bd3d0700219f812fd8e 1 0 \\x000000010000000000800003c7ccda951394998f625446f584b8eb4e7c6f9de591717e5a6bc0b25e7d72ff11cdfaa9edf367ea9730302dab8054598dc488cfbf509a04c8c5f56f4e376d81f30ae1b225a9a0116f340f57f6c4f6031ab478c28a1235c8f48af9d1572c9d786f34cc0b6b7ab1534d7e3660c055529bcc55625fa3cc833910d76c9c4c0bffbd51010001 \\xc85e56b220c0d7c8b9a75ec17f37a37893218b312cd6801ded405b991694c9930bb82983becd1ab0452ed0053a11b828fc0339696e6824ec3870c9d5c5c5ef00 1652874941000000 1653479741000000 1716551741000000 1811159741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-399 \\xca1f16f1be37c3910220942ce3381557014148f719b8d3a909846b2672ecba3b82b578c845d3f7a71dc311c268e4ccaefa6ef01615ac7b8d9adb9161faf2b297 1 0 \\x000000010000000000800003bf0c74d27094f02300923d41edac812dfe55982ac0cc160487d7b412bdcea9ecbcd1f23726dbae073df77fd58b7098cd58ece4610404b1ec6506aa72f6d42ab54564c0f9a2425a1933bfa279d17ac4dab44bb209cfbb54c4ce94aaac5293db26a9ea6dc0f6b8d655c083424b69f78c87144b0335032f56606aeb80059f432051010001 \\x803c93acf15ba54619b85478038339a6994e1f15801efe4ee31659e3e95f81657ba3a2b41ad14c844fb64b8a4390473617881236010ff0eda3bec29d09afb506 1663755941000000 1664360741000000 1727432741000000 1822040741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-400 \\xcb9fe635277fe51d852f7397799d246d6626a82dfbce57690e08029cf76abb6c27e4d322aa12a8b562e47e5dce820b451e43eba5f1f32d91a16fe303006f43ef 1 0 \\x000000010000000000800003b742510595b0ff33613f5bb477940f8141b6183eb72face0c6afa2379f3787ce9da0a2c44dad9a929378cddff25cbb613b8840615b007de2d79a65553bd247959a70926db0f2743a974518b4e158ac0cb696be493a6bb7cac2e5e4e78bedc5826fd3e638836c6f4305e778ae730211f12895eaa04cc49f7a11edcdc72e5dad7f010001 \\xa640e24a6c3773d9419099ab6cf6e051ff213a492d115845d3b4b43ce23339bdc92e7abf62b111a292c6d2bcd38c730be38afaeda18160b4958d6b1af8489f01 1641993941000000 1642598741000000 1705670741000000 1800278741000000 5 0 0 1000000 0 1000000 0 3000000 0 1000000
-401 \\xcca3f1425a9bbf19fc3d8e159bc1e82d6ec9535925cd1372c4b2abf4f3d2e9fd1188397bdd542316c864ebf3fb38c7654e2c27e36eb8f3e0a350633cf38e3661 1 0 \\x000000010000000000800003b37934c40249483645febdc00ba427872385b9d5d8bb8b22529f5eeba7288082e34781c927bb47f0108a4277d3f0f0bc11eb776306ef1b4b361ef0ee87781d1c71edc7f5ce2f203c5a353c47a5089ffcee9d08dadbaef83e371b2030d835b9297f722d3dee7b22f8b64dab1ad2769a2b5cd8a0f8783d6e74298671c2c77846c5010001 \\x9b4f202f8603c93e0b5fdabafead0318ecf3d11b20dd2aab046d6bd28dfba8f12281f0ebcfad5f3bc958593e533356dab468a8abc0fe3d3254508227a4c5d80e 1654688441000000 1655293241000000 1718365241000000 1812973241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-402 \\xcc1fcafa24cd7b32a961767d6356085a4b01ba4b2c31ab95fc5ba815ff97cb7467ef1450416dd9e8e3be7ee3dfab82da7147b1485f0bd3eb6d729f31b6d9644f 1 0 \\x000000010000000000800003d185f9ef4809fe901f779d480213e70bb9d9118705606364752297a0dd213ab8f2d6ea20695025167a4cbc3ca9e03e4345d1dbb141b53ef272f27526103b180fe105b5c41f8089c3812522a6e9e082db7c4a04c44958343927e54367cfd46e983ef9175cbd34a2bff47ca727a6c3c953d9b9ddb2a7744c72c0f0502422dd6fa3010001 \\xa69d411b25574883100dfaad66f3bb3f6e59ba3a7993246d4237f809cc19ea74ada45b4ea2926b310f9a5658cef49b2df464911826e353511222d41008bf200c 1651061441000000 1651666241000000 1714738241000000 1809346241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-403 \\xce433f122decfece95c95fb00659408944d70a62f91a10feb5909adb427797563879a919941737f0dbc07d9bbbc4cee9263b937bd630892d3bfc71694c561fe4 1 0 \\x000000010000000000800003b5d4f2250634d13c693e6ed5d7d6ab124473f591f5fb8fad90128cd83897f1348588aeb8b87d73028bc1f0815cf7df83f0c5b111ef57700069e26aa7456f09bae6d9c8339aa888ec040c5e055857315df2bb75528d0a4ec7911aa4a09a4eb876b771d7ad13d653622cfd7f3efe934f9bd84f80ddd7fa3917e5618bf50a234aa7010001 \\xd607c9a59b3ab914dbd60fd080e987b71b81b5d4b33f4f690a2d0a0134aaf578a01b32f69e0002a424b5e06206aadb762520b409220e2f4a9123a9081be31603 1660733441000000 1661338241000000 1724410241000000 1819018241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-404 \\xd077add6aa614abfd92fe2807cb43c7eb52e41473a5f5739d7a1103e3a5abaa57ef92a0e62c672be20c97dcedb40b9d8a10f459a611031071a72c485643a5b07 1 0 \\x000000010000000000800003ea19bc56f70f9de8d08ad386713f428b69de6ef4c05c1423479c6c5a8e303d957ebbc01d25e057f3a8f60ec36126d8e9addf74bf31a9e4a9684e80018a0a1433fd16c7906bbc3121f0e35b49a7fde1967e779c383512cc919cfc8ee4df70eaed4cad2dd9d2a9b457b0d59f2499c6bfb2dc2d870840aa56de693f723c2180d46b010001 \\xba5bc6d4ed6a1d211d148df3f76256801487f88187630b05f0a7eb275ef6333881dbc859df8074845e5e4e26d7e1f12ddb786beb67ebe5a4567e5c96431ceb06 1666173941000000 1666778741000000 1729850741000000 1824458741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-405 \\xdbdfba18050f3716660974a88f7e3baf880f6b6630afa4f95cb7905bed731ff146aa0da45993957fc400b46a5355dce992f61cbbfcfc4e24d3a3b1b219ffe7d6 1 0 \\x000000010000000000800003e026cca824eb16ccc3d2d71a7284df49ec4a079befd2fc9b914c4b60253f0698f5b86c7fd0d8000d7b6a6f9a7aea6e9a9823d52f635c5e2d0541fd41d90b970ec69e533657051a2263bf9d967f3bb32bdfe59b62d22955def860179ac1a37a9694dd6f2f35015641ba169e4f7cd66c83402cab980544379a2f5438698a826f43010001 \\x7a45caa035e34305631a79902e8c67fd70c1d6e4112c9d8ed51c99c7ab02b92d7d1509ed53310af47a0c8163307a7ca6b0b50b0c6f9b1b4e7c65c425d6ae1203 1643807441000000 1644412241000000 1707484241000000 1802092241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-406 \\xdbffbac9896c9c8a9bd54dff46046b764aff6af0a8794a61c44bcd23f96e3b052943d59d2c13011103cd7872cf4929fd2a9b00627d267612e8a56a04cf5907d0 1 0 \\x000000010000000000800003bb7d1cceef242f764332971a9cd2c63323169ca4ae90b1f2f550df4b60f38a05ba1822e7490dfc45756153f8daaac6ee3ba83e1176a1141b485f2902632713aec383a0f31d1aecf61cea14133b0264ae4c44af8f8860ec25a3398ed1d71a207366d0e4fbf44182d216a3234a99fa418fbefcdb42da4bd66959787d653de1b529010001 \\xad59079cdc2832bc444004560aee3bd961339eba1b03d108234b03f5824f595a5cd12df11fd926b6c13f804e188260fd41ad8125a79787eab8f39bc9e4bfb809 1638971441000000 1639576241000000 1702648241000000 1797256241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-407 \\xdcfb9b1cd6b1b4572246ab4bf71ef6b87d47e279cff534bea266a537fa917b8a31e2cc086fb615c7cd7e39024a48550b20921668e8a1cbc56715856a4428f79e 1 0 \\x000000010000000000800003b72a3e02bf0df088ba43da1f2894d73daedecf4cd68f7643a9b0f0624582989bc8e2b0da7b7e0c8869bde10fd13680fe0744ced1343705b08186f1f5adb7c3c7825ecdd4d50af32f7c893a5edf428d34a14dfea5ec1de656c7b5c95e8c5950565271ae9fbc8081bba3671d30e5b2f2ff084240c3d11875fd886b3df52c801f25010001 \\x5ff6e007897f61caf93fc2641d8678a1ff90c0e4bf413619bc366975b3adb4850882d13ac17d06b3ce0eece6e75bb78e65d2780862219aedb6986ab4f39f700a 1640180441000000 1640785241000000 1703857241000000 1798465241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-408 \\xdc2fa1ca9dc133ec3566476d4c8821ccf67acc7bdd7dd41cfb394750dfc18baa1f1a6f400c5468575a519827ef8b5a2a50cf8ba41bb68ae66847ce153dd1a031 1 0 \\x000000010000000000800003d730ed1aaf13897a1f5a6370505fc15ebe5e055b9250b02515d85cda2039063f17c9497b271d7002f058127ce1760805b1ecf0a71a1d708d40a2d98a5cef294f5a0c1fed4398dd1449ddb1d3775991f8748ddcfcc6e513a7f1676df38704cb97c2f5b93ba2eb7c197a74fd5eb8f540a5527a372347aa2cd0144bfd00b99e0aa9010001 \\x65af38ab40de4f6bb644ed7241d48e067127a4ecd136e8e6616a614565843c619ec2bb2c6c1702ed0a09324b66eb433fa2a7f3efe3105218527cd6fc2a6d130b 1659524441000000 1660129241000000 1723201241000000 1817809241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-409 \\xdf57e9bf552d3f8069456ab17abe5e271946c60470d3c45ec2e72f1337041d78372a3f5214d6189e29392701f8bc5addf89e9f54e2aba27ec98013b3ee120f1b 1 0 \\x000000010000000000800003d7115b55bbeb6922feffd27f5e646a51e1afab6b3374b14a42de68db589015c1c20b7df6673017adf928d57f292a86e2125ad324085997040ee31a7e112c350b729d6bc16fb65a829e8a73768fd2b4513c56a0f6c79fbe1245d06bf8390c98d0f208fad8bbd0ec73c2457a33f72ad5c4a9a5d4821a63f72b325797e5681926c3010001 \\xf7ea14e707971da6c6fb326b668f2b45b3d27fc003629c8c5178060392fa5ac7ff7d2960f219ef7c2d4157fca3654ba3ef68c12735fd06dc6cba103934f38905 1660733441000000 1661338241000000 1724410241000000 1819018241000000 10 0 0 1000000 0 1000000 0 3000000 0 1000000
-410 \\xdfb3de8d94a6b50b68b3e3c56eec54b0ad51bea52f26da8cb26190a6ffe3be1bff004631e7d4cc9a78aaa4f518d91cedc428bc38fec66c759af995ad810abb9c 1 0 \\x000000010000000000800003bede262b11a02e27b6d8f340f1a17d32da27447700b0444587ac8eb92dbac381374c7b23b3f80da2f5e4c650852b8db9d65a17ce40622eb1043f96ec5b8ee11e0fb098f122b8b2c830317e7020c9fed23398db65ced007313321af3d23c7b57feb404ef404513e79d03c663853db7cbf8a07ed38214d9058c46b0871198f7ded010001 \\x52d920002037363d4c6eaf53b25b3ddd64d11a445c909c3f93d7c7936226693282f8959a212b235c20083d12ccc42f1e42a9ed31ddba86a00b106f57ffdad30f 1640784941000000 1641389741000000 1704461741000000 1799069741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-411 \\xe2c3c0718e7bba758e08d67387176ba1408e8d07dc7eabad6aa1679823db8a6c470d3e3c3c309c8e1c67d2fe7f89e2d6a4184b3bc5b4295bae6b039ae813037a 1 0 \\x000000010000000000800003b3895302f664bd5b9c049255a37d8e9516d4025f851538b97f3a4a5bebf36717dac739231548f8d98782d8603e97a52e34fd62bc25c5913d047c35256e3cedffbb5563631928490773839e4c2d06df42e4097e519016410484579d05e3aa2714a31870f2bfaa78d8fad9c473e0c8d6f73cc8eb3b8ca38fb47393d99af86fd6cb010001 \\x528d30185287a4f3374a50281d193619e6bdfd3ecf3ba3db03f4619131b8c009de814da8a1ff3929f30584296fff2a732537b054dee13c5f9f1a2a651cb6b009 1657710941000000 1658315741000000 1721387741000000 1815995741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-412 \\xe37bb44a3bb7aab6b85948bc6b5ea93dfe5d423aa0443053a6b4e6eb21b173cf3ae2719640916992d566c424a17eb59fdcfcafb9cbb4df78eea156fb7006d50a 1 0 \\x000000010000000000800003b871ff782a778d6db9dc10ace9926d2b7c357f14cd4226ec4658db9ed01344cbaa04f993c9a233de3c93267f1ef5a084fd6efdd1c11f86ea3cb90e0ee9f0e9c008e99a0a72247f68edd16936d0d8127c7216c485cba0aad378a3e26561fe4b85615116be6bbe941648bdf24405a1dcaaf9e56b116085aa2d3ca2108fc5d6f55f010001 \\xebaed6bedce35b49c7f756c280e3875e2bd6417e0656416a797a8df903951df51b2cb76859e11699e375b10f0ae6fa702e1c54c215d4c9a1affe82eac7907d09 1639575941000000 1640180741000000 1703252741000000 1797860741000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-413 \\xe4f3dfc73e169df4c3d6d8e279617c0e481342ccf31f408ad15c7a3dad3fbd8c37195ecadf8ddda23af73a7d1189f414b4ccdff55d25165d510741fde0691a4a 1 0 \\x000000010000000000800003d1577dcf1fdf3a8e5d9462388e40c7180f66a0e6b254c25f82551bed13bee2158dcddf8df7e9d1ab0fd544868ecc995266ccc70576731b3b7e78ae686718fd3df4d63b7f36e05e3fe427f23b213b9b1b76620668a962ab7a2d68e5df7d7f05729b1e984c20a0e776a873310e8222b80cf9290ad434a7c225bc3e3865e29f4517010001 \\x854fe9efb4d98c968094d59a0f2bf3aed0090adf2c0d62c9c9e8573678d65d7767af525ab7ed43638b77491a7e32a3204a29be1c72c158314b339d8049956000 1643807441000000 1644412241000000 1707484241000000 1802092241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-414 \\xe54bddca1a76e4843c90646b979ff23ee3a64cda8a703871a2a03ab49ab2f8bef4a74551689115ce161d2199c68f37565ea1c3c2c85bbcccc7c5d542782451ba 1 0 \\x000000010000000000800003bd7245dd28d1db47d7d777c8a09614d8bde1f3ff3edc2a80878f5f9de72b474a8f46c6963896b6d9a88aab313af63062b09de7ab394f365ca718c176ba737085d9359c2a30b3da871b9a951920bac8f44025068716e85a36ade6e2de87baf90c5861c5919efee35ee06e39025ff0b4a53bdda4f2f30f97ef4b277981e310c591010001 \\xe9259b35a4edc0c4966e0aa4570a7bc7935a7290adf366dd92770bff6e6656b3e85c755bff0515c2cacb210bf7955562ac5e1cc965a352da68807b746fba8403 1655292941000000 1655897741000000 1718969741000000 1813577741000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-415 \\xe74f5b204654c1bfd964308db62c84749ff2962118c73ce8bd383370edf031882a76eb517b508d79a81b84eadf49a6c675c6b21db39f21168d5993aafa48830e 1 0 \\x000000010000000000800003c86eabbcdf777d311f6d6ffd1812d319ee0e537a7001e8f0fd04e25505f27c41a6a956f0fa909f5095ff3ad053c899b035657e264bcf66434fdd46493a1bbb5ef2e4967e7fa95ecf70a47734c2390426b67fc5dd1ea383978913bd557f57293d2e8d5f341c69bf110b6ced04ffbb7b71bc2bf0e217bc1bfc7e61d494c1ed6339010001 \\x79929032fb0a2e5dbd4274bcfeab8c18e5f0f3f4f53ce3496e2bd892d795616e3c91566ac180283fa37b0987abc97575ac47aaa62d49b7aa73fed91cd16e3e03 1647434441000000 1648039241000000 1711111241000000 1805719241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-416 \\xe8ab8b6cb9752605cdf6525dc4c8222d4ad1158ed3581460e59d3d72b8d153f9c7d8aaf25bf9d024c4cec44a1b8f9c386bdb942cfd96fcd7d56b4d897d6d4c34 1 0 \\x00000001000000000080000397a171c677503f8204221158b9d91654e7aa39862eb2c827dfdb41d08d862fa25d697e744e438afbe73dd07c5f7783c3d9813d00f285578372a3c2ebb94bcaa6a8f1ce9fb7d5079bed630d7588aeacb5793b59203510e62995b2720f25819f5aa65dac300e00605cbd367c88deb50931d93b6daa03f526bd4fed7e1ad1ef9b7b010001 \\xf8a195ac5172fef3f3c8d4f89bddbc648f2e05a490101b7e7bb8dd1ba80ace8789f94e660c6c64209e81ad45f543ba9a31f6711bd6759304fec909948e4c4a03 1663151441000000 1663756241000000 1726828241000000 1821436241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-417 \\xed4b1aa39229de6b7f3ca9e2cef140c8212e4805a6709190bec77c2d6aa2288fc5eedcb53fcdb68b27db40fe51792e54266c9720dee48ce68e322b2cfef81d19 1 0 \\x000000010000000000800003bc10ee4fbab09921573331fd66289f926e2686cf9251f2620064d21b65bb6c672894b51bb3e33c2b948de29ce946d8d10376b70901b5653b93e011b590989db89e295dd6ecacda502bb4e5cec5260ba6abba5241ac1639e5284bf5a4746dd51a1cea4d3981da9d2c1a9e356c367cd2a0a6bbf16b036fef2c9a8cfe7d73e5c9fd010001 \\xcee0919a33bf0a5258bfc3a1554cdf6c2d52a311c417c1619e37d259719a5c71a715efd1440b9928b7a9e30d519d957ae0d1e8d7c0059d3727fdb2f809696307 1658315441000000 1658920241000000 1721992241000000 1816600241000000 0 1000000 0 1000000 0 1000000 0 1000000 0 1000000
-418 \\xeff781b3eb7e34f795939d0f9684b6adeddd4d816205b0508054fdd9e6803d8742adf78986c28ee6a9ec914c2f28ab53bd1495043b69a7d1b1c8814afa4d8b55 1 0 \\x000000010000000000800003b7f4a975a2d195b83d959a70a762376ee2c03effca6ba84e5dd3d7fc09c6a99bff1d91da17e12e63bbb59d793fa0a60dc0a4b85053f5e560faea0a67f6f2aa85a9f8c6e449455fb8ab7cd2486ec3f7ebe505284f3a99053bc729feb96af2faf9945f007a697521585e04389a21f303e87ed30f6b62636b00bb3ea534d03cf247010001 \\x6b9b65b1809e48fe67e085113656c133a33cc89b395a713f3de948df81948c0af9b1e959da5f4a8d75cefc1535d514ac65a0a6dc0a4bba2bbcbb1e36fc7db40f 1641993941000000 1642598741000000 1705670741000000 1800278741000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-419 \\xf3833e9244db82be991db2f44a69405176c0dc70a023e90d16bdc9fde4b492ac1fce0979aaf78f17236cd6871d0cb5b1dcdae41127c35a7d873613dd87cf12f3 1 0 \\x000000010000000000800003c26f47e07c6cf3db541335762df029f17d5c9aa3cfbf0a58b68dddaad3688f500924fc7cf77e7f0288699910158dda337eaa4b53ca7b5dd876bf7e3e15a6f9f4dd4ea42e1caeac3702abab0d7e744ca122949563904d378f4fd95b1cf0b15e3fa6726a961955554985c076ea25695b0e1b74cc62f90b1d24dcf626eb875573a9010001 \\x83146e2c5e3118d47057b9f77d432dfaf79cf4f27693cd84c9328276ae7c502953152ebeaa8015546cdc46efc453575fba1226d5d9fa3ef914db690da3bf430b 1657106441000000 1657711241000000 1720783241000000 1815391241000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-420 \\xf36f44751be59c6e69ca2b312decf4ba502fafbc4ce9aaeb33723336979eb51f6cc9090fee72bd6c6ec5bd4b858a8fb732a9451b5cfab2df1f88addf037326f6 1 0 \\x000000010000000000800003ede8d78ca527403433b37b3da2aba37fcfa4bacf0dd72bdd0a202383a6fdb20c1dba628d30cdf66777cbfa000cb3a00adfbdd203d2902f6611c826c385174ef35ea4773c4225511f4a8276059bf09425b9c964d0a8ad7a64489edcf62fd31377f264edc981785551aaff61db6be16b9b65ceef2671f43cadf08c54f32a007fe1010001 \\x9f582b16d81d96722398b6a34f43ef3ac1d879150e04fbbd2dd7172742541abe0c3e41a73a538555646b8808e81f57b554c96242830b8d7ce1d8550111f3090e 1667987441000000 1668592241000000 1731664241000000 1826272241000000 2 0 0 3000000 0 3000000 0 4000000 0 2000000
-421 \\xf3a3b30bbe27b788097d7a94f535b63f6f1fe45225033aa965c3994f34ff3a6ac7f055afa75e527bb9fbcdc5ddfad89a41c84cbfc1eb9fac97149012e1c8b408 1 0 \\x000000010000000000800003b942c43db27be3d33909d923f4e03208b2917ef5f12f7358872a14be2ce328c6d7b42015738b20a02587d094a1fc8b8fff74237e0eb58383745ae6186f29a8bbcbefa2159ed797366d5e91e51592394659b2f04651076e44b04f52f83a011d2619ac8443f7ec2c88c8f5380dacf916d63fbd7a68ff2a9469e5bf4e662b4d3cd7010001 \\x36d6aed24c64e025df33a24707c8e2fef9aa317e6573e41b6db5a58bb58e77d28ff69bcfb67b3f7defd4d9a272efc2bd000f9b2293567a506a2f32c8f52f270a 1640180441000000 1640785241000000 1703857241000000 1798465241000000 0 10000000 0 1000000 0 1000000 0 3000000 0 1000000
-422 \\xf4b7e2748781e52ac477ca8772743ab7373eb2e3731b097f9e096c53e55b1627dda7c7523bd848665b1b3cc64b0ff27ea6834dbb4b6e20e03c53ef731b5057f0 1 0 \\x000000010000000000800003ba71ca2190c7c817d040f22c24a88651ef581fde2e2fd77ecfa49402018680a7036aadaeca6ca5b5808e20d351833263fc9cbb4b6fc49fafcc206291db9f97e1157b132d0930b8d5552950693cbfe3cc53f8b1fff88f85912befbe5bb23bfdabb7c89f113e39577ef6f531f8558afa44fa32ee758405392d4b965f1e2e71ab47010001 \\x42f14043b02bd030808b93db5124827e08cf3323b97710644c53f404b23f596d3439ffe1aea45c6c7594328c6a396055e15acc956c768acb1e259f474092400a 1663151441000000 1663756241000000 1726828241000000 1821436241000000 1 0 0 2000000 0 2000000 0 3000000 0 1000000
-423 \\xf8b3fa627081ec67f2d2a7410d250f8a6c8c63898051a363cd5c183973de0b12bf2451627e433181f92541828e7999dd7b8cb848b6036ea4726a0bc59ab00ce5 1 0 \\x000000010000000000800003bc04a44c36871c07828350d7a5c7ea1f76ec51bc6a03a7b571ce629477f022a86099b8d58693bd1ac60684545b8c6ea901170922d661a9d383f009436cb58845c1f1597aa36dc9c59c4aedfa69ded329bf57b7a2c740e574aaaaf5bab3c1bb5b3f565976f2be9fd2770589d9a09ca06f1c76e5916af9282150b2ae3f879f8c81010001 \\x7eaf2d488d985ae7ad5365b79f602b843df7bd353a6b576cbaf24b3789e4d3b9aa58997b317b6b306090e39f4a7a06676645ac384392c6eab3928a095ad1f60e 1666173941000000 1666778741000000 1729850741000000 1824458741000000 4 0 0 3000000 0 3000000 0 4000000 0 2000000
-424 \\xf8bf78861dcbe30a3fe5e847ed2885ec22ac744584840e127440a19df0e2b062a475e4f96f80e8297fdaf4a2e86b523f27486a747f61fd8ed989ad565a9d26a0 1 0 \\x000000010000000000800003bf9518bef6caf0d6d6cc92bf27a0211284bc9dd8539b982edae99539a21e1966100911ecd44f12a002301256f0e85ce15ed2ec6e1cb161b7db9d05df57a4f3b7a1d0e3a7ba1fe2ac99f0b457f12a9e5170d4f89fa08c50e59183eade6d7bc6d377fb36186bc473ce9f43333b3bd73d2604d5b5cdc58bf0f14f2803816b1cc6fb010001 \\x91ffd7617cec83053485a282698ce0905b743966923c8b6a5ee90cfc753303b223d691f607791ae67873ad262026c7c58662635b684bc68b130ede3cfba8310b 1655897441000000 1656502241000000 1719574241000000 1814182241000000 0 1000000 0 1000000 0 1000000 0 1000000 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_extensions, h_wire, exchange_timestamp, refund_deadline, wire_deadline, amount_without_fee_val, amount_without_fee_frac, coin_pub, merchant_pub, exchange_sig, exchange_pub, master_sig) FROM stdin;
-\\x48ed82513cc149c010ca237982a36246850491b26723e0568be0b28a9b8602b4 1 \\x4fe9e894a158a6880831024afb7768cf7e2bf63a0dbe851684770ec4fceae82037ce1e83e9994c88fa00c032fea966233753431cbca0e372c0e869fba879ae81 \\x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \\xc17edb67fb6fcda6be90eb485f2f68f617b6e51cc469db0757ca91f933c99bf01199f09519c503f4c830074cb4a7dc8f64d95044813df28801364f2f86df6252 1638366975000000 1638367873000000 1638367873000000 0 98000000 \\xa1413938dbb85af180050a3164a6664a171980c1bf8775778cca5b02f9ed078f \\x28c90b2b1874b667b33ea54082637095c75c33e3857fc25a4ec81b6ec4c31d8b \\x4f0abb980d4559b25da2a9013004b5e2b2a3747e95f272fd9a3c5d6e61692c4a2dcdc310e690e62f1e49c8572475c2f1402eb94516244b888128511dcecf3600 \\x7381fdf0de425ba7c87466c78b506c9f453784258154f22784ba2f4159d1d267 \\xcbf64f03d2550000cbf64f03d2550000cbf64f03d2550000ebf64f03d255000011f74f03d2550000cbf64f03d255000011f74f03d25500000000000000000000
-\\x48ed82513cc149c010ca237982a36246850491b26723e0568be0b28a9b8602b4 2 \\xaadf168e1276328a987e5dbf06581844c0b6e5e508a4fd99289d8a9d2bcb071bd11a554c51e2db1cde926a8805fe8e81b1d961e62cbe0a753d5358005d199308 \\x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \\xc17edb67fb6fcda6be90eb485f2f68f617b6e51cc469db0757ca91f933c99bf01199f09519c503f4c830074cb4a7dc8f64d95044813df28801364f2f86df6252 1638971815000000 1638367911000000 1638367911000000 0 0 \\x09cd70968a5e3086cbb191a45afd0f0e96c578e671fb829723649fac30082571 \\x28c90b2b1874b667b33ea54082637095c75c33e3857fc25a4ec81b6ec4c31d8b \\x674671a0193eddea5bc2f651ff55e3778d092ca76a031ab5627679e5c5e49878be483c71ca8294a5b41b2023212fd36330d3c8898cb1d7251284a3cce6a97306 \\x7381fdf0de425ba7c87466c78b506c9f453784258154f22784ba2f4159d1d267 \\x6b245103d25500006b245103d25500006b245103d25500008b245103d2550000b1245103d25500006b245103d2550000b1245103d25500000000000000000000
-\\x48ed82513cc149c010ca237982a36246850491b26723e0568be0b28a9b8602b4 3 \\xaadf168e1276328a987e5dbf06581844c0b6e5e508a4fd99289d8a9d2bcb071bd11a554c51e2db1cde926a8805fe8e81b1d961e62cbe0a753d5358005d199308 \\x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \\xc17edb67fb6fcda6be90eb485f2f68f617b6e51cc469db0757ca91f933c99bf01199f09519c503f4c830074cb4a7dc8f64d95044813df28801364f2f86df6252 1638971815000000 1638367911000000 1638367911000000 0 0 \\x13d98aeecb195cef3fa231ac98ebc998abca490f09d0d0fe9ebc610ade1c3233 \\x28c90b2b1874b667b33ea54082637095c75c33e3857fc25a4ec81b6ec4c31d8b \\x86cc74e494459812974d86a13dccea2118a3f87d5a8a602bd5700503f4df724962b9e96942197bc2c6587bcf70667ef4b1f3dd792310ca102a75478fefe97f01 \\x7381fdf0de425ba7c87466c78b506c9f453784258154f22784ba2f4159d1d267 \\x6f245103d25500006f245103d25500006f245103d25500008f245103d2550000b5245103d25500006f245103d2550000b5245103d25500000000000000000000
-\.
-
-
---
--- Data for Name: deposits; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.deposits (deposit_serial_id, shard, known_coin_id, amount_with_fee_val, amount_with_fee_frac, wallet_timestamp, exchange_timestamp, refund_deadline, wire_deadline, merchant_pub, h_contract_terms, coin_sig, wire_salt, wire_target_serial_id, tiny, done, extension_blocked, extension_details_serial_id) FROM stdin;
-1 1382188551 2 1 0 1638366973000000 1638366975000000 1638367873000000 1638367873000000 \\x28c90b2b1874b667b33ea54082637095c75c33e3857fc25a4ec81b6ec4c31d8b \\x4fe9e894a158a6880831024afb7768cf7e2bf63a0dbe851684770ec4fceae82037ce1e83e9994c88fa00c032fea966233753431cbca0e372c0e869fba879ae81 \\xc3019d5a47e68b46bacae12d0a240097b204339a063ffac5e13f00ebe0ab52687ec04d338a1226d87169c061febafdf676fbdbe425911c75bfedb551474f5f05 \\x71cc75fe4952d09324d003da4118aa08 2 f f f \N
-2 1382188551 12 0 1000000 1638367011000000 1638971815000000 1638367911000000 1638367911000000 \\x28c90b2b1874b667b33ea54082637095c75c33e3857fc25a4ec81b6ec4c31d8b \\xaadf168e1276328a987e5dbf06581844c0b6e5e508a4fd99289d8a9d2bcb071bd11a554c51e2db1cde926a8805fe8e81b1d961e62cbe0a753d5358005d199308 \\x39c544e9ebd403b075dc5bca95b072cf35ccf8f10dca293449ddb1863ddeaab9236352216eb5d3402f4f898af062d7b163037faa9d082d2906209126c43cf809 \\x71cc75fe4952d09324d003da4118aa08 2 f f f \N
-3 1382188551 13 0 1000000 1638367011000000 1638971815000000 1638367911000000 1638367911000000 \\x28c90b2b1874b667b33ea54082637095c75c33e3857fc25a4ec81b6ec4c31d8b \\xaadf168e1276328a987e5dbf06581844c0b6e5e508a4fd99289d8a9d2bcb071bd11a554c51e2db1cde926a8805fe8e81b1d961e62cbe0a753d5358005d199308 \\x54cc11f856384da4bc295222895a1d454b2c7dd60f4cfa0e9120e5ed7e142dbe0fb489a9ac086996dd97f4473d7c9572c2603eb06a41f2920ea044629a111c0e \\x71cc75fe4952d09324d003da4118aa08 2 f f f \N
-\.
-
-
---
--- 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 2021-12-01 14:55:41.279577+01
-2 auth 0001_initial 2021-12-01 14:55:41.382701+01
-3 app 0001_initial 2021-12-01 14:55:41.450952+01
-4 app 0002_auto_20211103_1517 2021-12-01 14:55:41.453042+01
-5 app 0003_auto_20211103_1518 2021-12-01 14:55:41.455097+01
-6 app 0004_auto_20211103_1519 2021-12-01 14:55:41.457776+01
-7 app 0005_auto_20211103_1519 2021-12-01 14:55:41.460569+01
-8 app 0006_auto_20211103_1520 2021-12-01 14:55:41.462687+01
-9 app 0007_auto_20211103_1520 2021-12-01 14:55:41.464554+01
-10 contenttypes 0002_remove_content_type_name 2021-12-01 14:55:41.474418+01
-11 auth 0002_alter_permission_name_max_length 2021-12-01 14:55:41.482812+01
-12 auth 0003_alter_user_email_max_length 2021-12-01 14:55:41.489571+01
-13 auth 0004_alter_user_username_opts 2021-12-01 14:55:41.495386+01
-14 auth 0005_alter_user_last_login_null 2021-12-01 14:55:41.501046+01
-15 auth 0006_require_contenttypes_0002 2021-12-01 14:55:41.503096+01
-16 auth 0007_alter_validators_add_error_messages 2021-12-01 14:55:41.508524+01
-17 auth 0008_alter_user_username_max_length 2021-12-01 14:55:41.518685+01
-18 auth 0009_alter_user_last_name_max_length 2021-12-01 14:55:41.524606+01
-19 auth 0010_alter_group_name_max_length 2021-12-01 14:55:41.531793+01
-20 auth 0011_update_proxy_permissions 2021-12-01 14:55:41.538824+01
-21 auth 0012_alter_user_first_name_max_length 2021-12-01 14:55:41.544625+01
-22 sessions 0001_initial 2021-12-01 14:55:41.563641+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_sign_keys; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.exchange_sign_keys (esk_serial, exchange_pub, master_sig, valid_from, expire_sign, expire_legal) FROM stdin;
-1 \\xc09775c48b8cee11d08ba85669165ff54ccfe4c3232a2f1d206151b76296e945 \\x8dfbf026aec87e499c0eff6b8fd4e00fa02906d0d3495c824399d34385c34b61f264c483a224c68f210026d228cb0c9187252653eb6d2d973a32b753c408900d 1667396141000000 1674653741000000 1677072941000000
-2 \\x21480f729e3e722a811a9261b0ee7ce65ad9837dabccd82fa944ef9bf61be83c \\xfc791fc5a0e770feb0bfde4e5ea823becd33c097a2954e73554c163ff2d0dc8c651151ce7e6b001bf11b0d172514142e1a8d2dbdef1e735c1fa9b3a601be5c0c 1652881541000000 1660139141000000 1662558341000000
-3 \\xb07d619aa2def3e5a311e5c2769821d3e35045361f6027f6075ac4bb608ad091 \\xe7119f45abe5088bb2ca954935096751626217a7b6b68c94b63db037129b38388c17ba5d17443bf66ef4925b42a044149f7b1d37ce1aacabaa75b736c1777609 1645624241000000 1652881841000000 1655301041000000
-4 \\x7381fdf0de425ba7c87466c78b506c9f453784258154f22784ba2f4159d1d267 \\xa0d298ebc68805af02cb0f994d48b4c7cf88c9356a9ce46ccd55bee9053fc558462f46d171acb343b405e45d7429ab1a3fcfeef76f4269cfda2a88b656059a0e 1638366941000000 1645624541000000 1648043741000000
-5 \\x74087dc33a9f3fdecd032a452e565aa8b7241bb6dd8c013298eafc2b3541fa6f \\xff394faeafd8ccf73571c3d6de12b822c219d3423b6de29cdf2e5e8484d0e6165dfe6a1fad49c6c94f3b292d29e6bcbd05ad93093cb8eb69d5414110826c8805 1660138841000000 1667396441000000 1669815641000000
-\.
-
-
---
--- Data for Name: extension_details; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.extension_details (extension_details_serial_id, extension_options) FROM stdin;
-\.
-
-
---
--- Data for Name: known_coins; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.known_coins (known_coin_id, coin_pub, age_hash, denominations_serial, denom_sig) FROM stdin;
-1 \\xe0186bf5b3febf38f761e298be270898c434e30dd87e29f58ac3d4078b56fd90 \N 92 \\x000000010000000027229e865b66cd52ddd3f59f13551670ff4f51b50f6f602d4c4f45f27d0a68be407f628a4da14e6b8bee309c0cb305445b2ca27f35cb1b45b43a8b56ade4209831fb2c21d6d40244f1de733590a7139f7e79f9f83aa18e99569aaf0b9749c8166ceeff024870d844ba9a77df49482c05664560503d6e2288198c41a3b0d83002
-2 \\xa1413938dbb85af180050a3164a6664a171980c1bf8775778cca5b02f9ed078f \N 134 \\x00000001000000002f5616d8bf60d805d528a76067eb31c8e927b6c7a7b13e1d50c312cf3ba41aa9a85e0f74d88c0e9786847adb9ecad65be64f3ae162472d37a1814338a8fd9e95167196d310683e05a9d1b73a38623b09e0686a2b358761f22712f0ece7520fef5f9d147c624c69dd29c13f73bec7a760804c0b2c4f63fb82a15f8417f80fd7a5
-3 \\x728b8b6f25aba26ea7402af16fcc3ae6bf3180fd4e7115a714e52164d2c12558 \N 93 \\x00000001000000004d762e535c9bb8cdce66f8227fb5d91144bc00c49cc41a58ef1ae97c6b7d158fcaa25910fe5689c985c588652493c36f380716be0aeafe1483f5ca376764026fec0106bec061a145d8addbe9018e5c478038aa39d14c31917a91582cb209ff608315013fba52468cfd33ddb313b5f619f798bbfd26c6c05e965867f66d6f23c6
-4 \\x20902e167b6e6d105471a1193e57e4e95e69472ec0c3109eb26b198d6be377eb \N 364 \\x000000010000000095b82d67e39eda301d7cac22d109b63dbe2345dc09f7611722f9f7a6033968c58316eb76694a1d57a48c2ddb6624e4e55e194240233f668c12889d1f4d709862e9968d7324c3a4fb32028a07c925fce4871c26235a98cd0d89a908074ab7f493445a8020a2685f302c309a0f1f0431494e2a83a85c91e96dd1dfcc7526f809ad
-5 \\xee2f5dea39c17cffb7872b274affccc002cf6283a67c392228e7f15b00f9fbcd \N 364 \\x000000010000000027525bbdff513800e7b8a883b174ab8f9e3d1aa36da5c431b863aa6a11963286fc9f3b091375c88100c30e38215dc9b93a5523aa5727325e60403714f618b7f42f56e86583112debc26525dc0c7bae5f342b00a3715d4be33e7906f320e0d808be7cdf603e87020de930b81f85b62f02f7355843221389ed23f8217cc23a375b
-6 \\x475a1b16b5edad2f9eb50a3b953ab83466f7bd60f4164b43d5de6f3096e84ff0 \N 364 \\x00000001000000007f72118902ac0946513fcad4ba35894006bd3043eb52e7f29717045276642dca23d5fdca2fba7ec613b5011a9ef4751650593f0602d844426f63a6b74dbb9c04a6daa11f883a84480e3f75031393ad953a65441513ae24de1e269f635f0d82bc3753211d828b5a4c4ad460202f7aa619df788be97a9200ace888ab67eeb93bbe
-7 \\x69324983d0cd4727fb97daa6d7471b2f9fc8840d05219274a76bee146efb65cd \N 364 \\x00000001000000007f9673351b68b149c79a945806186acdb90bc73fd166ecfddf97162246e3353a472a3486221146ed6c5e378bda7842961493b55916a1c59d78a7fb9f55fe6743ab4e906515b748846e77ee61b5fb9ebfa55bc7808bbb24f5e523936ce7efd23c5a1e5ba76ee0981b10e30501a5e961b414aa4cd4d6f8f203969163a724a496c8
-8 \\x6dfafdc0ed2b116eee101cdd0c4372a9c567a24dfa626d3a9db0fe975117b514 \N 364 \\x0000000100000000b60e9b8b64572002792fa087e6bf432c4eb37497b0b9301ed4be7f6521988147b27a708188d7f7fe16b2dc1750839a2f5134480130b8025004a84e5c77a8103a2a09f8eb2b96245ce85936b9050068b4ede49f980348e3c81f6534f89e8684cdfc3aae8792e6fbfe45b9e8ea7746ed6cc74998ccbe6967c9f7d6daccf2793586
-9 \\x755466d3375b26369bed54c4113d5106dd531609531bcb9e1e3eda13b3633653 \N 364 \\x00000001000000009a4ce4a19b35c81e181228d8fe32cd06a3b46d36833137aafce50d80944fd526555fbc0f90b45b81b9bc4c63ccca95a2d1e7b6397fccb1200674e7f8928f33de342a33837b7bdcef85d6420d63943070166423803c29cdd7f92b0d1f91322750b35f86081c236de8a5a97513f12d54755e596901911e77d630bca2f120c15f88
-10 \\x82b08ecc189a01691e5500ad6ad870d287aae25234a1cf02a1e51b6c9770ba6f \N 364 \\x00000001000000002e91a348c3efab2fc3c97755cc93aef4c6a9be5a717c3be0498c2c2d6e2f7f787699dd7f33990fc17a312551cde306adfd059fd3f9d2481a64bdf78b4a9627216c254c9b44134828efc29f263570656414a6fd09d10c1b869bc1db969519d4c9fccd228f1bbdc9716f5bde7e40def6d9d673d67dec4cd76246b590afa8d02104
-11 \\xb521a0bdc5ff821d6309e71db85fade321871c708b49eded381d1eb225124d37 \N 364 \\x00000001000000009785a9ae858a3672cff7f9d4db8209f821731ee95a9a7a1a26f8cc733b15d0cbc60d6886150c9a81c2a0eb27ebab0e1b566c9640e0c1ea9e665ab42ddd95e0c3110b19a0870a5b3e265dbe827c6d895e60edf0871f65fc8deaec1ecbcd9ae8dbae13d3243b5cab6f2721a30c722afda3aa3662d2ceb5e1b7568720a565e44c15
-12 \\x09cd70968a5e3086cbb191a45afd0f0e96c578e671fb829723649fac30082571 \N 264 \\x000000010000000084271e0f735f3460197868bdf964c54174a269a55975cc276b057f1f072b020aeb2a161f052e43ad366003ad8a59a683c279e37d6188bd3801dfafdb461fe9c594b4d0eecd9a126d2b8b79017442ca602a7129b9c8755f6052748eca65e4c6823adf2382c6620836a7a47874bc0b5b5acad3c997b1f067fee68cb84b3f9adfcc
-13 \\x13d98aeecb195cef3fa231ac98ebc998abca490f09d0d0fe9ebc610ade1c3233 \N 264 \\x0000000100000000659d040133cbacee2f875fc579ceb6481a1a90dc05e77d414f27241381e7c08613261c79345c9eba19af74a0771a8d68d0d6822b2e3b1c0bba91f1795d3e324f81671bc07c23ddff2f64a17c54c323e93d482db7c5aa7c553c4e2da8914325c6ff2e45c68cab401c20791d5556e478c63bcc32508b13cf9ce9e6f15df486b6d0
-\.
-
-
---
--- Data for Name: merchant_accounts; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_accounts (account_serial, merchant_serial, h_wire, salt, payto_uri, active) FROM stdin;
-1 1 \\xc17edb67fb6fcda6be90eb485f2f68f617b6e51cc469db0757ca91f933c99bf01199f09519c503f4c830074cb4a7dc8f64d95044813df28801364f2f86df6252 \\x71cc75fe4952d09324d003da4118aa08 payto://x-taler-bank/localhost/43 t
-\.
-
-
---
--- Data for Name: merchant_contract_terms; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_contract_terms (order_serial, merchant_serial, order_id, contract_terms, h_contract_terms, creation_time, pay_deadline, refund_deadline, paid, wired, fulfillment_url, session_id, claim_token) FROM stdin;
-1 1 2021.335-02G3H7DHA6ECW \\x7b22616d6f756e74223a22544553544b55444f533a31222c2273756d6d617279223a22666f6f222c2266756c66696c6c6d656e745f75726c223a2274616c65723a2f2f66756c66696c6c6d656e742d737563636573732f7468616e6b2b796f75222c22726566756e645f646561646c696e65223a7b22745f6d73223a313633383336373837333030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313633383336373837333030307d2c2270726f6475637473223a5b5d2c22685f77697265223a2252355a4450535a56445a365444464d475844343559425638595242564453385752484d58503154515341385a4a4359394b465231333646474a4d435741305a4d53305230454b354d4d5a453859533653413132383246464a4830304b434b534647564650344d47222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226f726465725f6964223a22323032312e3333352d30324733483744484136454357222c2274696d657374616d70223a7b22745f6d73223a313633383336363937333030307d2c227061795f646561646c696e65223a7b22745f6d73223a313633383337303537333030307d2c226d61785f776972655f666565223a22544553544b55444f533a31222c226d61785f666565223a22544553544b55444f533a31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f222c226d65726368616e74223a7b226e616d65223a2264656661756c74222c2261646472657373223a7b7d2c226a7572697364696374696f6e223a7b7d7d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a2239335052344d395752353457303436413444575235385632385432473934444a43574859304e4d42573253384e36573630415430227d5d2c2261756469746f7273223a5b5d2c226d65726368616e745f707562223a223533344750415252454a5636464353594d4e3038345256474a51334e52435a33474e5a5734504a45533044505848363333503547222c226e6f6e6365223a22484844595258584e564252534a56314d5852574853503937505433443138464732394745445a53544b57594b4a394b4a42575047227d \\x4fe9e894a158a6880831024afb7768cf7e2bf63a0dbe851684770ec4fceae82037ce1e83e9994c88fa00c032fea966233753431cbca0e372c0e869fba879ae81 1638366973000000 1638370573000000 1638367873000000 t f taler://fulfillment-success/thank+you \\x17dcb3df058180cd09068e1394edd744
-2 1 2021.335-03P554SREPMMJ \\x7b22616d6f756e74223a22544553544b55444f533a302e3032222c2273756d6d617279223a22626172222c2266756c66696c6c6d656e745f75726c223a2274616c65723a2f2f66756c66696c6c6d656e742d737563636573732f7468616e6b2b796f75222c22726566756e645f646561646c696e65223a7b22745f6d73223a313633383336373931313030307d2c22776972655f7472616e736665725f646561646c696e65223a7b22745f6d73223a313633383336373931313030307d2c2270726f6475637473223a5b5d2c22685f77697265223a2252355a4450535a56445a365444464d475844343559425638595242564453385752484d58503154515341385a4a4359394b465231333646474a4d435741305a4d53305230454b354d4d5a453859533653413132383246464a4830304b434b534647564650344d47222c22776972655f6d6574686f64223a22782d74616c65722d62616e6b222c226f726465725f6964223a22323032312e3333352d303350353534535245504d4d4a222c2274696d657374616d70223a7b22745f6d73223a313633383336373031313030307d2c227061795f646561646c696e65223a7b22745f6d73223a313633383337303631313030307d2c226d61785f776972655f666565223a22544553544b55444f533a31222c226d61785f666565223a22544553544b55444f533a31222c22776972655f6665655f616d6f7274697a6174696f6e223a312c226d65726368616e745f626173655f75726c223a22687474703a2f2f6c6f63616c686f73743a393936362f222c226d65726368616e74223a7b226e616d65223a2264656661756c74222c2261646472657373223a7b7d2c226a7572697364696374696f6e223a7b7d7d2c2265786368616e676573223a5b7b2275726c223a22687474703a2f2f6c6f63616c686f73743a383038312f222c226d61737465725f707562223a2239335052344d395752353457303436413444575235385632385432473934444a43574859304e4d42573253384e36573630415430227d5d2c2261756469746f7273223a5b5d2c226d65726368616e745f707562223a223533344750415252454a5636464353594d4e3038345256474a51334e52435a33474e5a5734504a45533044505848363333503547222c226e6f6e6365223a224e48483250394357475a563035574e31534658593846323432483333434e51504a5657594131414a384759374351435039385830227d \\xaadf168e1276328a987e5dbf06581844c0b6e5e508a4fd99289d8a9d2bcb071bd11a554c51e2db1cde926a8805fe8e81b1d961e62cbe0a753d5358005d199308 1638367011000000 1638370611000000 1638367911000000 t f taler://fulfillment-success/thank+you \\x517f5c871f3bc9b50635679d3c850bf3
-\.
-
-
---
--- Data for Name: merchant_deposit_to_transfer; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_deposit_to_transfer (deposit_serial, coin_contribution_value_val, coin_contribution_value_frac, credit_serial, execution_time, signkey_serial, exchange_sig) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_deposits; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_deposits (deposit_serial, order_serial, deposit_timestamp, 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_serial, exchange_sig, account_serial) FROM stdin;
-1 1 1638366975000000 \\xa1413938dbb85af180050a3164a6664a171980c1bf8775778cca5b02f9ed078f http://localhost:8081/ 1 0 0 2000000 0 1000000 0 1000000 4 \\x4f0abb980d4559b25da2a9013004b5e2b2a3747e95f272fd9a3c5d6e61692c4a2dcdc310e690e62f1e49c8572475c2f1402eb94516244b888128511dcecf3600 1
-2 2 1638971815000000 \\x09cd70968a5e3086cbb191a45afd0f0e96c578e671fb829723649fac30082571 http://localhost:8081/ 0 1000000 0 1000000 0 1000000 0 1000000 4 \\x674671a0193eddea5bc2f651ff55e3778d092ca76a031ab5627679e5c5e49878be483c71ca8294a5b41b2023212fd36330d3c8898cb1d7251284a3cce6a97306 1
-3 2 1638971815000000 \\x13d98aeecb195cef3fa231ac98ebc998abca490f09d0d0fe9ebc610ade1c3233 http://localhost:8081/ 0 1000000 0 1000000 0 1000000 0 1000000 4 \\x86cc74e494459812974d86a13dccea2118a3f87d5a8a602bd5700503f4df724962b9e96942197bc2c6587bcf70667ef4b1f3dd792310ca102a75478fefe97f01 1
-\.
-
-
---
--- Data for Name: merchant_exchange_signing_keys; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_exchange_signing_keys (signkey_serial, master_pub, exchange_pub, start_date, expire_date, end_date, master_sig) FROM stdin;
-1 \\x48ed82513cc149c010ca237982a36246850491b26723e0568be0b28a9b8602b4 \\xc09775c48b8cee11d08ba85669165ff54ccfe4c3232a2f1d206151b76296e945 1667396141000000 1674653741000000 1677072941000000 \\x8dfbf026aec87e499c0eff6b8fd4e00fa02906d0d3495c824399d34385c34b61f264c483a224c68f210026d228cb0c9187252653eb6d2d973a32b753c408900d
-2 \\x48ed82513cc149c010ca237982a36246850491b26723e0568be0b28a9b8602b4 \\x21480f729e3e722a811a9261b0ee7ce65ad9837dabccd82fa944ef9bf61be83c 1652881541000000 1660139141000000 1662558341000000 \\xfc791fc5a0e770feb0bfde4e5ea823becd33c097a2954e73554c163ff2d0dc8c651151ce7e6b001bf11b0d172514142e1a8d2dbdef1e735c1fa9b3a601be5c0c
-3 \\x48ed82513cc149c010ca237982a36246850491b26723e0568be0b28a9b8602b4 \\xb07d619aa2def3e5a311e5c2769821d3e35045361f6027f6075ac4bb608ad091 1645624241000000 1652881841000000 1655301041000000 \\xe7119f45abe5088bb2ca954935096751626217a7b6b68c94b63db037129b38388c17ba5d17443bf66ef4925b42a044149f7b1d37ce1aacabaa75b736c1777609
-4 \\x48ed82513cc149c010ca237982a36246850491b26723e0568be0b28a9b8602b4 \\x7381fdf0de425ba7c87466c78b506c9f453784258154f22784ba2f4159d1d267 1638366941000000 1645624541000000 1648043741000000 \\xa0d298ebc68805af02cb0f994d48b4c7cf88c9356a9ce46ccd55bee9053fc558462f46d171acb343b405e45d7429ab1a3fcfeef76f4269cfda2a88b656059a0e
-5 \\x48ed82513cc149c010ca237982a36246850491b26723e0568be0b28a9b8602b4 \\x74087dc33a9f3fdecd032a452e565aa8b7241bb6dd8c013298eafc2b3541fa6f 1660138841000000 1667396441000000 1669815641000000 \\xff394faeafd8ccf73571c3d6de12b822c219d3423b6de29cdf2e5e8484d0e6165dfe6a1fad49c6c94f3b292d29e6bcbd05ad93093cb8eb69d5414110826c8805
-\.
-
-
---
--- Data for Name: merchant_exchange_wire_fees; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_exchange_wire_fees (wirefee_serial, master_pub, h_wire_method, start_date, end_date, wire_fee_val, wire_fee_frac, closing_fee_val, closing_fee_frac, master_sig) FROM stdin;
-1 \\x48ed82513cc149c010ca237982a36246850491b26723e0568be0b28a9b8602b4 \\xf9099467bd884e86871559a62a7f23b6e876bf084a30371891b5129ce4440d3cbe27afe387d39b2ce8d9625abd388517c81bfc8da9f2e0f8c9471bff65a802b2 1609459200000000 1640995200000000 0 1000000 0 1000000 \\x403ddcb1ac9ae4bca73e915a1c1ef3d901b0c2a0aa4dcdfdad12276f18cc5d438c4e64057b4e11c76f8d05a3b5d6dc56fed992e05f0d76c06e892680b9458408
-\.
-
-
---
--- Data for Name: merchant_instances; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_instances (merchant_serial, merchant_pub, merchant_id, merchant_name, address, jurisdiction, default_max_deposit_fee_val, default_max_deposit_fee_frac, default_max_wire_fee_val, default_max_wire_fee_frac, default_wire_fee_amortization, default_wire_transfer_delay, default_pay_delay, auth_hash, auth_salt) FROM stdin;
-1 \\x28c90b2b1874b667b33ea54082637095c75c33e3857fc25a4ec81b6ec4c31d8b default default \\x7b7d \\x7b7d 1 0 1 0 1 3600000000 3600000000 \\x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 \\x0000000000000000000000000000000000000000000000000000000000000000
-\.
-
-
---
--- Data for Name: merchant_inventory; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_inventory (product_serial, merchant_serial, product_id, description, description_i18n, unit, image, taxes, price_val, price_frac, total_stock, total_sold, total_lost, address, next_restock) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_inventory_locks; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_inventory_locks (product_serial, lock_uuid, total_locked, expiration) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_keys; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_keys (merchant_priv, merchant_serial) FROM stdin;
-\\xe761e368e6cdd79523b5f53b2cad49a3938caebcf7d3c1ac336341dd751a74b6 1
-\.
-
-
---
--- Data for Name: merchant_kyc; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_kyc (kyc_serial_id, kyc_timestamp, kyc_ok, exchange_sig, exchange_pub, exchange_kyc_serial, account_serial, exchange_url) FROM stdin;
-1 1638366975000000 f \N \N 2 1 http://localhost:8081/
-\.
-
-
---
--- Data for Name: merchant_order_locks; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_order_locks (product_serial, total_locked, order_serial) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_orders; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_orders (order_serial, merchant_serial, order_id, claim_token, h_post_data, pay_deadline, creation_time, contract_terms) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_refund_proofs; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_refund_proofs (refund_serial, exchange_sig, signkey_serial) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_refunds; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_refunds (refund_serial, order_serial, rtransaction_id, refund_timestamp, coin_pub, reason, refund_amount_val, refund_amount_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tip_pickup_signatures; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tip_pickup_signatures (pickup_serial, coin_offset, blind_sig) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tip_pickups; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tip_pickups (pickup_serial, tip_serial, pickup_id, amount_val, amount_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tip_reserve_keys; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tip_reserve_keys (reserve_serial, reserve_priv, exchange_url, payto_uri) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tip_reserves; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tip_reserves (reserve_serial, reserve_pub, merchant_serial, creation_time, expiration, merchant_initial_balance_val, merchant_initial_balance_frac, exchange_initial_balance_val, exchange_initial_balance_frac, tips_committed_val, tips_committed_frac, tips_picked_up_val, tips_picked_up_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_tips; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_tips (tip_serial, reserve_serial, tip_id, justification, next_url, expiration, amount_val, amount_frac, picked_up_val, picked_up_frac, was_picked_up) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_transfer_signatures; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_transfer_signatures (credit_serial, signkey_serial, wire_fee_val, wire_fee_frac, execution_time, exchange_sig, credit_amount_val, credit_amount_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_transfer_to_coin; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_transfer_to_coin (deposit_serial, credit_serial, offset_in_exchange_list, exchange_deposit_value_val, exchange_deposit_value_frac, exchange_deposit_fee_val, exchange_deposit_fee_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: merchant_transfers; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.merchant_transfers (credit_serial, exchange_url, wtid, credit_amount_val, credit_amount_frac, account_serial, verified, confirmed) FROM stdin;
-\.
-
-
---
--- Data for Name: prewire; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.prewire (prewire_uuid, type, finished, failed, buf) FROM stdin;
-\.
-
-
---
--- Data for Name: recoup; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.recoup (recoup_uuid, known_coin_id, coin_sig, coin_blind, amount_val, amount_frac, "timestamp", reserve_out_serial_id) FROM stdin;
-1 1 \\x0be3f090d5d6ccf514532472983ef2636617c542809579bbc9df20e9f108b1ae00b37488e54fea5eaf2621c13a7827ab00ae795a73ca8b0aa430e5955b74df05 \\x807b9cff040f410ed34ecc188f990b1d47eff7fcbd14b1d17bc2c209587e83db 2 0 1638366969000000 2
-\.
-
-
---
--- Data for Name: recoup_refresh; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.recoup_refresh (recoup_refresh_uuid, known_coin_id, coin_sig, coin_blind, amount_val, amount_frac, "timestamp", rrc_serial) FROM stdin;
-1 4 \\x45c50dd48da5b296a0e4b93ffd3c5a3857d8dd7bd99d82a4cea444ab5e6ca19c2b278d2021ff2522ac92cc31b245a8fe619d07c5aeed4684991d70dd05705704 \\x3d546f3cc5e433f3990f118640a4f30b1ce4a09639e21cb0f8fe9bf7ca885093 0 10000000 1638971800000000 4
-2 5 \\x486b4a0ee92e0644266cd93fc98bc72a0e6d8bbbdc3ec9ed4fbeb560d40d0fd5bd99f2de0cbe67b2a5c374b7f552eb7131d49fae2de07a34b6e198025f5dce0f \\x7273b371015234b67d1ca12af01736a0fad2e4d888736eda83c2ebf6de6c8d83 0 10000000 1638971800000000 3
-3 6 \\xc7c4f026fa362f1a38f980b9e83e3df73562050bc14174fef9b0294d255f722687030a32ec10ecf92910d2c753014b972d4f5e49a60821451349d55599d35802 \\xb91ac2ee8bb986207af7f94d3e90c41d2d046ee1e31d3c4fa91be408a01f3839 0 10000000 1638971800000000 7
-4 7 \\x29467ee969ee8f8a59848d9d474e51e4e867b1b44bb5133bd5f602b2e726a1e521afea26c30056d9ab0897b9f40346dd12019a7dfe172d7417f9399922de8c0a \\x73193a9fbc71eb1995445f0ccdbbf650c9d81cda4bfef6724ecc371a1d10a2cc 0 10000000 1638971800000000 8
-5 8 \\x750b2484c67d8a4740ba48ea4d636894d4e06ea6beea18ad87ce82c09fad21f5b00814cf871edc3f48dbb346164bd192b9bb7a57beced52e0b96e52084de4701 \\xe11653dec0c2540236840ab680a0560bcd9220ac22a0d3207ea64f9107e1b4a4 0 10000000 1638971800000000 5
-6 9 \\x5ddc2111398eac860edd72374bf3877d843861f1d76c56f647860bc3f7f3b6a6f43ff2c77b96c0e9c35c07d4c219648b9dee42106354055296a46775466ad60c \\x9785a7a7c20bb6afe7827a4983c6d0e7a5255faab81513323c38ecf04a808b73 0 10000000 1638971800000000 9
-7 10 \\xd3cb8364b2194ea92c766db0e6a1a233910ca3f045abd9d34f778b7eac4a624846745ff2d2576b80d4ff5a620ecf3e20c36f2b867878fc56234d71ca374c3e04 \\x37812d2a76662c592341f29469ef84e16080f3bab1254aaf18008cf90312dd84 0 10000000 1638971800000000 6
-8 11 \\x1dc368e868ee72db51579dd745226ac1d7668537a3b6aa27ef533e3c9eb61ecc3f387381de9bf19aa4663bb68179fe2a68248c1b07c2d3954f20a8cd0330a40d \\x50d0adc0817193083ae0654a2f4f13a4bc58e8f51c3eb7df7623c04211012332 0 10000000 1638971800000000 2
-\.
-
-
---
--- Data for Name: refresh_commitments; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refresh_commitments (melt_serial_id, rc, old_known_coin_id, old_coin_sig, amount_with_fee_val, amount_with_fee_frac, noreveal_index) FROM stdin;
-1 \\x1a0b0eb52968acb6bdfc2599f4f8d54147fd90e4efde730bfc26abfc295abaad3a46439beb62df108498960faa9edbc7300c7daae1a83ea8fbebe44a9eab885a 3 \\xdd3e5f19563d35f68eec4dbe4be41b3edcc4799e7f4708ba1081afa15f566a4e8e392774acd54fa7a04d0ab65737e541c6021fcf9f02f4d92c7df4d34a93ca05 5 0 1
-2 \\xfe095f2b3cd7cbbed98a95d7426c45604cbd4d0773ec908fc540c3901d6e64f5df889be6bd7937a3a26dbdd6c7d708756cf7a95c81c0d8811803d9c612e39e1e 3 \\x1097dd8cfcbbd0120c9b8779e758a54a7989ac62285e77e7d9498bd4283abf93555ef3528ae399f1dd42ac3732aa75b5c0ac8d7bb9bbb2153465efa3eeb4eb00 0 79000000 0
-\.
-
-
---
--- Data for Name: refresh_revealed_coins; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refresh_revealed_coins (rrc_serial, melt_serial_id, freshcoin_index, link_sig, denominations_serial, coin_ev, h_coin_ev, ev_sig) FROM stdin;
-1 1 0 \\x1fc0177b5a1801015180ef7b61a85f9f598a524f024c0153a15a7baef69eaf22cab2432d36b0394dbefaab11f3887a0876ba939644b87cf800c9bcc2db185d06 62 \\x18e5bbefe6693c424e0d4348005d772ff83d1c03fc345e239dbd5e10a57213030a97e2d13ac9b07d786311530ba8dea82baeb5f427c3f093a6792d92f58f9863995621b6eef754f3c281578dec66d93221ecb43a55b54a6c3e98aee7fdcf797fbf7a88ad6998b771020c0f19d3f737ed645beb528f6ff1c4f31781c6209e64bc \\xaca7555d3ba80dea0fbf31883bdd249f2b17223734357e7c4f66035c037e9af58bb6cf91420586ba89f17a5d2516d883636b9449860c1a8cc8ff23ec9211ceeb \\x00000001000000016d5f09da77249e882cafc90089f9f50251d4af54b7f4bc345f1eaf0723027663ffce1b9b1e7cfb6eb094c87dbd9edfb81f6929cd570c1ba5cf1a4ccbe30441ab6dd3c7841ba9b4662023fb4d148e82a31370ba09a1440c41eabf941ca082f7fe37e7daf431cd937dee301dccbf9d82967671ba7463cfceca4d793b411d92c116
-2 1 1 \\x071493f53d1a2d3c3e2dbabd3e596878c0b232bbc54fdc6609154c043da10d8c09a9b660d84a05c529be9c33d14f0f15e30e9e65d643ae307914256f26a1660d 364 \\x615152e42b77c4a2c2ccc8e588cde22a0fc96a7add99bcfcc7d7a9ae4431294512cc6665973002a1c8cb3090323402c93b009f204cb8ce838d415d8c18c716c9e400fa9505b38dfb32434643b23fb5dcb125f0288b632413aeb01b3fd7ef670a559de96d1259bd218c5160f19494063925bb1149645c2c87c3f3ba45ecf5facc \\x63c1827f442c35e1add6272308dfc148c748fd68e973e95c264a27d1f24f62ecf3c350d35a63836c029c71e338a60cc525894d439d5daecaebad242ff515eed3 \\x0000000100000001655c7d3a686266b0f873b307fbab5bdb7f30abd07393fe8b68534d1e6673ba9d65f5397d5750ba033ccc659376e7615450b2355d38961143d83c66d49100268a85a2d1a040d0ee8a41fc442883cfa954f1b67f57c92be243c97aae5bd963f905c8c2e82e1dfebf17c4110fc6fddd004eda8fa09a98bdf6094f28ab5ef063b7d4
-3 1 2 \\x132d13315e707f02a9915f4da95176b4663c079f18bffa476a61e1c8bd5cbd0fa9240a5ace43ea7d008e389e16b58216acfc0a91a0a306f54f41d641d72f8e09 364 \\x0399a3f3b9f9317bea40d65f0f821556777cf84cb81b2d1c4ba4b314d771f33ecf7b39efc8abf765f4ff23a28d6fce367263c1f442fb000c9ccfde9b92ba427e6b8375495d92c539c0c61c42831471853dda4030d123d7b16c0fcf9733e7b92411f7d92819a6cd5cb5c4e9b3cfd535308bc41c236535bd6642b3e3c527a32f53 \\xfa1bb030624f246c6bdd844e0cacbe412c1264f56b43ea902c0de8fef78e241bb8851b9982592cd79e95fe03147c45a6137aa6cb6212ed77c3c34dfe9fec2d5b \\x00000001000000016f5dcfe9242c6d81b04a637c4f683b14013b111a69113114873c6dffc5cea2523336e3818c7ce3d0dca1d9880397bb47ffefbf1d0fa0a28726908229394ea3403f3c0e510f8b8d218d087b02bb0357b6992549432276e925517d229d8438e67230f63b309b1cd8e0bdc33ab05a7030e363f6d8d9f0411482433760fda163f9fa
-4 1 3 \\xb546608b00cca93a14613f1ba9433f4971606bf8739cc14aaca0156509af3ef5de5806f8d9a6a170f4043dc99712171a0388ac079c74bd7552a6a44b523ed302 364 \\x891717d560782516b0e53039dcce902061c14217e0b0d1bb1c674aead518e8a8a27db3f6409fba7369131a918e6d7c9a2816e510c121456ff57fb328414d3d4ed656ed42afb6ca1225a7d3f4943aef4971da913f755b6b49deb4bea8599d371a4fba7daaec1f805bf713463e948653d8234988ac63de9a05a2caa198ed10a98c \\x04d64041888c2605d83d2bdfe5b27f9ab626e70feea4174167bead984e75299050cdf948e6243bb83c26c70b556f205bbfeccc2e4ca001c5f00025a4c208654e \\x0000000100000001b557915fa19a0de7e6fea0a1390e7f8fd8ae0cbc65c958b51ade7e992e26aa5f2693ee8a4bdb4d8b1486149796f7f6e07fc8314ac580c3a116536df04647dbdf51b97c6c191a6ed617ca6326b46f94a1a59906b948fbafd3faa3ad26a9af63c5b1aed88af5d244c54c98e5881fdb941f3a23fad582c7c1654976bf38cd452151
-5 1 4 \\x1fe2ee3f053bfbeda25b7acc61a5367a84db2c8c0e0686ee6f7637949f5271ec91fca762d19d04aa75e52ed7d8955d900bf685eb726b4f953707e1867dcfe00e 364 \\xb420691ab9e489317197bfa8ca759c65ba1104250a74d7f1da0da1c97975227645f943d146b5f93031e919c10101ffc6626298ba92654e750647fe41a57bf2f60bcbe64a6ee63f234d27672023ee75904f94501aa88fddab28fba8ea78fbf7ff5a6c4294903af54fc1a3f95d8af07d624cc6915f9ecc9ebac6e17a910cd79049 \\x27eecf0dc3854aa5cfe681277df01da9ba4de2c2136b2a0d0a842474f62699a99aa34aae957dfafa3a896a6ff98b9db0f3ff5cdfb603144d727aec9edc5723d1 \\x000000010000000136efa206c6c0a3c6f084b336a1244bdbc1a5c3628e4a528d704a4358e0a4519e17a7c563210600608deb860ce07af623a870ddcc39e168e6838049d7f4b7339dc5e8981213cfd4ea9ff33ca0616dad1dc4f580f302d89d0a7287a988924b58e814fbf091be8ee792470f49e94ea6271f776bb1f90bbba91e2f27343d3d78658a
-6 1 5 \\xbf7415486b544e8a0b41a2c6fd766c0eb5196406b0aa5a1c8e6461474e4885b1a0b44448040593c00ea52a7366b2cd0d96d1e6bb52a6b32fd9f7f112f5169b08 364 \\x40ca2342d40fb89b2e60154cae37e7f1fed21ab0271c78e7ed14d662ff8b42e94b5335830252f8fd8d44f2fdad1a5a9a9b53c38a9312ef06aed8ca28a5f3af145b3847f6dcf18c4a1ac151e2bde09f929dd868aee155ef59fecf8076de0675fe3427c60346f93f3b7ed11bb1b6ad59d803d68bff368d59640336d1e7a61d3b96 \\x6ba172edc2704115ec3e4c42f0159c931acf8f6f2867329b70462c0d33689983517918b8107d2961d54a10b3534f429ae7990a2ba010d12ad472e02477e24a7e \\x0000000100000001430d44473da6bfd141ecc9ecce15105e5f4b53a8730a7e2476a735c28e3395d3395e589fa26829529eb5aae9f001bbdfaa1eb7c6f5b652f923fb3916e4a4726f86d86007816de120a867bf8969ee5701ebae35feb940a5c1bb9106b27ed9ba46b45e1c7c2b2343f515ff31a5312b93ea36ab2a9cb9367cbcc95ae8b3be38b390
-7 1 6 \\x0ab4db1961bda6a58067d2cfecc0b874369a62265e22e0008142ef16a95c90393c05b479f1694586e08e11bb888026493e70bfba1881e3d311e48775e9585d0b 364 \\x0cb44948e661f8db43dc1d285a6c7d98d62336fcde072250b44af359bd65e9bc2c3ed48448d46f9de94782057a9cbf0b9011cf8d03f0427a5ed25e9d6b25bc1711b2ade64dcbe1e2f376e1b05e5e22b386ec0d5a841ce646599a408c0120944b48cd436dfc773bb31c8434b57b1b869243d25ca9673f798e6dcc42711eecd9cc \\x83985d6a978715a8e7ce6977cc72f6e2c6e902643a131df12e42d432aed77be5c1072688d5a9720e8eb55c8e92f3ac7780519044b38e55aa158a5e6a98742bd5 \\x000000010000000197f2371b77cd6445fe366e58a87c0ea8d1bf64a6d3a2483f0a14ffa834dc2cbab4fa86876d4010e91ad1cd36b2806d6acd636e7c895b93d1604eacbda7301c08fd265adc2e1225ca4ccc119b0819c2e8a80a4027744b2e7a3a9baeb0b8ba578fcff96edc8348bec61a386a0ef91173138952f1d9d105b34ce8935f51e2951966
-8 1 7 \\x7fed579fa6e88eb03cdb6be9f7a84402f7c10fd55d0be068269b7b3d485eba4741fd14a55a974fe5eaef4c0f6a08b8cf940c85519b87f1c518e59bbb70028501 364 \\x6ce047ad4e0ccf0a7715472593983983064ab8e9b0b8cf966cd5a973f6f549b79805b60be570ef052e8b4588dbc37685f2ac02a40a586eef0082be43e23cce7f571a9a901032faa519884811442f8295164fa2109bfae76a93b69cfb26a3ff9852f07656b212ee53147414467ab1310b38a3a1456b82f695bcce4bb6d715af05 \\xb64f38a3928e5b7a0673505dc80875e07b68aab6db39d3b343a675968363337df5fbaf31fef7bed55cd59c2e26629b8ed5a117600801f3f09c315bb73094355b \\x0000000100000001aa3cbd8d54034d2cad49f5c309883e909223fa6877da2309e5f6f37e4a0ff25fcf381cfc4c869b9a2d07913c45abd66285560643f657297d1b3636d6da50c11e008787a70c2bc3215c4275b332379e33e581290a554b0b58ee0930a1fe4e6b097007723530d74365b579760e2ec49c035d233b37ca8f63c828a8aab1d7676d64
-9 1 8 \\xa3795c125b8b0e04f34c88afa0e7bce7daacb4ed67010602cb814dabb90b2e6b765e3795c5f429e665ec23aec9cf214f77e5043e7ba76bdfd78615c29cde1107 364 \\xa4aaa73e2850a54d33dfa9568cbbbfcf481aa22e2c6167865564bfb4e00d110819c77e59b59d1ab882e5c7a89ad7347dee6eb6e157c85c6f7fe183d4488dc66779291c9987c042f3cf609e7d2606b7862513cfc80da9d874b56b0d7c45bb4728078cfb336e7e613af613829418589a746035f2d98689ee72d3b4181f364ff3ed \\x982df3d5d7b794ee880dd30fef58dec13ec6a12a07a6dfb2f6ff5e8b3296ffad0b93abbba1d2cea56c13589d8d9259712acbb7e797f6081ff5132abf3aecd47b \\x0000000100000001543181e26ac8e091444d8bc02969d3fccc8d81579fbe70a63106b755d9887bc9b91ae9a5b923d202f29e425dd22a528608b2d2f9309b425400518d1ccdb31368921397785abc7a3ac6706f267799bdf6e58b7f3db9dc4d94e467dfa36b9ee6fc8f0a9668bd71075a11d9e0609ac9a429d5e4ff2868d192d686cea32fcd127172
-10 1 9 \\xef27efb296cccdb2ffbc32c2f1bb645e5e4bf0c55ab76ccf7d8019f5ac844b4004e005fe87ae2b8eef2319fa31825fd6cb2fe74e0cdc9ced6ef31cc06558d007 264 \\xa035bc86a0dca9e3471d80eef35a84131b5e6ed4924ccab23d0b0852832778e44fa9bb8b3e096c9b963da5d7939a21eefb63dee37e5319c1c06f9c929db05885ef3f9e897e08fd96e59f889e59d39031d2175f1cf9db8c364024e264921520adc6245adbca9e2b345805833866db2bdd1d2040bbed1f30c40aa1f55ff7a9cfb4 \\xd0035f8b2805eda1b054ea96b75d29af29ff501f4f0d204845b33ead5d075102997622043014cd8b3c912f1c90ba9e61149853573b50e7a5f2f6338fe73abd6c \\x000000010000000158e47f7033fd6ac4e8f96751ad96692032ead60d10f3798867a1d4f0bf773f8e9b75df7daaa5057709724448ff789e18bd8b453f824c3f06c7653f5500636554363204e2a329db6539075bcbe659cf2d5bd7772d52d2297a0f44b1de1e8399e45657daa6f7e0886e77a5e588aac2c513acb3ec99ef8a08629548da21600216bf
-11 1 10 \\x718831fcefdc74725643fdac28124fcdfaf0450173461861df94324d9c94466fd7b927edc72c3f847630179d374306b03183057ce431436779dba2c3d660f905 264 \\xb44e0e2d18fae27afb3abee4b4e4999f52bc371b15eebea2abc4c79f8c2109610a285c6ecc1a7b5f7032e53f87f7acf2350228eb18a29529fd4e9a061a0072447391a9a52360c791b017c19b05e4352532ca16e2a9857ea3fccbddacc57b4329609b4fc30a9d17a00261a17409d694076912e90e23badcf523c8be48226ff2d9 \\x55d3be3aa0de5da8d02e7059790b7976bb8e90b9cf361dc52a2209f7f0f941b18898adc2e39317419a1e7cb7673b381057a3e3b396d80b4ecbb237d69ee7a3f2 \\x00000001000000016b5fe6d30ca464590b836c5e4db15e12a0a94b5ecca12b655924e7a3c4e5c87ce4a373759434d2ff573d11562e2db33faacf670415d56d5e28ed68111c9724c4aee92168fa3ada31220984fd66ec5d4e2173f1714c71244d3eb4d105c2854bdaeed0b4041c6d64b6912b497a25823f2c7018f5f8c574f855b16521906ffa6922
-12 1 11 \\x2fb70b8ebab9e9fb1a407ec4f3e6fe1f8c6bd79c06e35810d4aad9d4c8e5f0c142d075e5135ed9acd2cb2af4028b5450396588d2cb452ce70635727ea10dbd0e 264 \\x7a6360fc08989948117884db598296109733b5fc4fc7462070fad7b69cc4a65f3d46fe51a3f176ad57d7dbc08c7e1422e30cffa381072a255dacbcb9c96cf071dfba983966c86b11756c405272eb08685136c8c493ffcef3c6c5fb855ca112438ef3841ca3f085198e2d80a0410336bea41084c93b436848e06cf8a70d6c1537 \\xe328a690f40b25d4fa3d00d0cb1ced96334f03e71f17ce3176f93b3e29a2d05dde576a12b2fb4b95c0481101920f80626f00c36b6569337a2332f3cf4fa68f3f \\x0000000100000001aba928c1e9621e6613f9f3ae95e75400319952d5abd0a5551e3ee4697c43e9bba5a8df8552e4075702527fe143b30bdd231c39bc2abf2aca8843e630dfe7fcf19ee7d85ceaf0a6446c43fcd9b0d4da7e97ae2a7ff6354d68ba9616a2fc1e0e7afd7535657f60b848e402ecbb01e282fd8940588f035a59b27f6cbc1d2960f008
-13 2 0 \\xd2efc0cea68967aa53d4d2405b054a9ede7ef545af3607e466ea42fa92d3f63f2b51326f6aaa56042ad247cf9651e4ca464d3fbe05f98f019c6475bd3aec7c04 264 \\x82f15b4d16df2e7c8cb21bd98863605f165984a6fb2e96d70bc44aa713c9362b2ddccc6326c7f01f3644db65f0dbf40a6aefdcaca3276c07b129920738e1db60194f713917777701d0de6b49fe4c778978f10544b4a0edeb9f6db4df6f9b13edc967be563de9130901861a90b1df2c816e8bce7773ff76698e087ed13a599892 \\xa9933d4589af8379feb525e44188ee6e43f0312ee9ce6925130fb1ad7c7162521ffe29390c409bc81a331bc1b73ae46c4b04978a7e0b42d15bc3539f27455273 \\x0000000100000001a4f10bae695ea2b01b6cfdb45edf3de8ac999e2afb3d781181cfba47a3c091781f324b8c55b4a740f7d821636adc91d93f8c7d112b255a21d1df64b216bfa5b843d575215dd235fe2a4350b70e42d913fa2fee63ef8057fbb80591b5e868d8806d74f5909d8e8a7766f3e86afbb54c4d29ff0aa92b4632b25ae37c9168f2b10f
-14 2 1 \\x017871874bb19b7b407974c69aaf4b9b496edeee1d71faaee4349e52443148560c4939281c9bd641989abce8f3b4c54e91bd42c705cef623d018e3e9a9b78501 264 \\x0d3ddeee9902788081310aff2de9b478fea9251af358bf44cf443108989e0d487aaddcc5f09753c7e4cda8b5c57bd2bb1103bf5f752f154a680a9f5b1b41b69c75ba8a67793b0b8bc224f6f785c7f08af78152105034248189b36733f126ee6f95968062e187eaf0e6721eb1375cfd5903ad7353dea51193337a6d1bc04e7278 \\x476b98317908f6e5024059650e26f509ff259a90055729a273db84218dafb00e57011994c8a0ece12b5aac23f75805c20b8ec560d9da619b922f231f676c9812 \\x0000000100000001be8537069635761baa92774feafcacc86169297a178786970dff27da66da3a7261393160fd316a3214da9fa4b91b5a30cbacbc9c6bfaa3e4bef423509a7c45362ac8c1b372b76293c1842bf3bba2b55fa91a7e2cbad94e40e8b899d32561831a28f10bc8d933a01fdb671e02fc4d5d2f911d09cad923475825a2ad3eb2d44f60
-15 2 2 \\xdd71a7e71fc17409ea527ea02cd0120801186d4d8b435c740b526b1e2f5fe14eba8b93726305a444941b3002d971c43d6730b8698234b57f0e9758dde4e1af00 264 \\x4186ea2ef92c29aa84e45be2f7e6c739f9e54881f3e686b24b1110fac6cf26f32d1c4d6f1772ff4f245e3084445e0f5f2939547d151b827b61c18f79b5fed1c176efd7d970b3908a1b6177ab44a06ec6dd07aa20008f1bfb8a010c175e8e8128d46ad9b7e27a81da3919d8ada5fc5fd0f342680dbfa0bc0aad96b052f6e392ac \\xe525c5ffe6c8ddbff7ad00f11b2435a21dc7dad9f27f0430897c1a46a808f9a2d9a4f927337370b6623c1595a694a5e21aec46a75cf0ad7a0e969667d3c8bc0d \\x0000000100000001021551474850876d72e2f8c924a64e9f13db798b90353d81a3a210eb02132fff40c2ccb3eb24d38c21c5dd06f5a8829f595d4b7e35bb484b899c439b69cabc63138c8c28c457661c6a355c1cecd5460678eb4110f7f83ebcc4a91f5cf61df1765b63fd59328ae345ca9010316e2efd6bedf35a4e7f3b06b0c3e5b9aebef3b55a
-16 2 3 \\xa0a11daac4ddf3823249fb5cdf80068a8990f626c965c8741172c3e4af959900569a643f0b429ea8c93b30181374be20d63ee974a08b596188f8cc14857a4705 264 \\x3b8e2e8300fda57fb7bbd99a4856553e22fa140e60197c3de2917b0e51ceed4485c7a2abbc060b7bc80c9faa6ade54782a826ec48f87fbd025ccc42de24ed878aa5f3d80f190e7483a649a8b29abc03c5fe3dd9787f9aa38e50a89a46b12dad2c5d2901537d6933c9510c61b3a21a7615beb79d5eba713fe63f29e3011bcf4ad \\x2d0ad0da6e0a37c8c28ae41fda8f442f01b8a0505c2c476a1334549a682e3ad794c8a40c6c45dd4eee09580949c7630f87b1d6cb2c193f1930d7ae229ac29b82 \\x0000000100000001a4c42269c402c8c346b4fe05ea97a6df23d6aa577a56887e5f2430874ee4ce0d7398f282d92db2741506d0fc02c1cc73eccbe32c41fd965f8443efec6f63a6e1dae20b437d4a28d49bd720aa2d61104e5da2e96102a63e042d9b6cf5642d5486787d948f64a6282c8fcff725bb511dcbab0e00d2cf3e0c5f953a63a5d45cc8a2
-17 2 4 \\xe79fd588e29aa3b6d66a04e2088fa4ffcda1e18290ef09d83d90baf39d8a125aee6214104ca1149e5058c0b09f58a884dd92ca9309fd47608c91ba6df8602802 264 \\x98c2dba37c613c40ed854d4762bf8b165a18224a63cf74442cf2a4d061c66489cd3b51558c076605bdb1992a1fcb02af10964c5a3b3c0ed4a9c9638f0fcaca73a8dcdfe31e4423e79aea9a384436f50faae72fc3185a2d8fbeb37f12d6f521d75bc622431280c1a126056afdd86daf56836b57f38190d94a699cc7a1f026af4e \\x7dd460452d34a201956d44903d036898209a3d56a6a367c2a3a0690e73e416612451a716dce325e46b7895208ae27f4461d34926feaa22a17825f39576463b68 \\x000000010000000109e77072ed160610ad4f538a4778fabff8893cff88a8cf572342ce63d7a0513863e12d3c4484b744e3a4d2bc48b891f20645ef43ac404323315ec6ed7acdd09e25b5d1d4142641d56bbad51dc73aab1a165471186e8a292a7695dbf6af0aad62480c00934b6121fb7eb3530a46fd2bae91a327824fe18ab3e951e4ea5d2b0dfa
-18 2 5 \\x4aecd4642089723fdd008ee9df9d9daeb8668c61f3233178eb0390bcdf061cd6d02139062de4f10cc2b4d10f3520ffde29886f9d6532f842a362558e8583ff05 264 \\x8aca85daec10d3629bef831f0532ad27eea45ae43384f21d2458e6d46c1992596fe10a4520b251edefbe511a53c7768fb07ada48bd1fdf6f82faa276a4ca52908199133fe5b1e0acb0a64a944e22d3e75bcece70144a7dca86df8cf315a51b72d2763a1f1a562bcddffad9367593b5150dd2042695169fb3412a0e53d6e21184 \\x70cf619ae3d0f6c3b3f8a3d1ce5821d5bd098e655856075703a050a0cca5555b3b76b1dfe54035f9beeeeea6b01864dd31c30236eeb45997f5b02736405f944d \\x0000000100000001297abd9f95bbb48a94798eb3ce99f2ac863a678d99a038c2edd6cc066a45d24801469c021ca9ca8664c5a365fd5802efd16d2af8978e372ca4573f1248a0511c7f9ab86081e9c4978d495eb9b0295ae08e5a40799acd6038acfb349636a01adda50397ef0d96c0d431b03dbf4b7980240d999b73ca082c34557090aa36f2f369
-19 2 6 \\x163b3c0eddab72c33a7caeb653749c650e904b0e4611ae3acfe48f47f7e14b60778481c4e7928951c1fa74606306a7b1f71f19a4944578adab9786a4ab1be304 264 \\x6fd3e829f4c5c174497b6fb61396dd91e72bf52661d407d8ad14eb15efdccca38a9051a3eeab4e7a6045089ea9aea6905fb89400dc31efa4b55c67e409be7ec665736bda0cc66bf21955817f40681be5175ec99b8a679c56ec1c50eb7390f3a40d46b589f646a1fd5fd703df61c70f89d0c37798c6fcbeb56fbbc41b09fe40a6 \\xb74b6351d199bd83472724bea7cc23762416a0e96fa7cbee8579015d0d958927cbcd1b92acf632dc2e366cc19b06265fb432198f0b5d91038432c94263d452b4 \\x0000000100000001675c508497f62a26b95d4cf4600c2a078330234adb4b9fb168fe9667d5658a202b3240ef87bd040469e895fa760a57e3689ba74d86219e10ef07b02e825f237ae34b1918c5e6386681d58a8aeebef74a28d7c17639847e1a4b7dc340c2183dba09896266aeab9a67f531c70c5ac6e9de298bc66098645593622bf3e79066fc02
-20 2 7 \\xc103d225cedce9a665234503d9cf69cd2c482b8d93c46f1f7785584848070e1f65508341be2cb12ffe857703ac380a7e40b2391af7b9fb7e018f680624f88903 264 \\x924f5fbbaa96298f375a2b36565f361d09f932c0ea124dfc17e6733ddc8d89eecac1b7b2b769ea47a7f6ec4b8c79dc56481371ff827f6951e90747da22bea45313808bf123d395438fb87b2096cc48a23c5e8ae5ea394be4ee39fbcd4ff6135ac64a7c87ff412ed8f7b586512443a1917b06ae639487884ee09bdeeecab69613 \\x05664b1c4c85563fe9cc62540c51229e256993746b117534ade242601b2653e78cc0fb9a3d36e49191a227d0686895c86108506b0d75cfecd67363362b1b2ae9 \\x0000000100000001ae5f55eb36405ea38c380db40be43b3706331211fa05779a9e774e8cdb923e522a4c5f33174a34566d06bdbbea10c93e3ef18c79ca815ff6b03ef36bc42aa247dd77880cb8d19d6b02db87c0a5dc2b26e0cd29ba1267038747f2346d5c2dec8ab56fc3a27fcefb560bd082d3e85b1a5afc32d16b9d9b4c85c3a9dc943910511a
-21 2 8 \\x2ec87f6019de0a198f6bdc8265729887c360f5d675ca0055e2c0fe13a6a334d103a8c341cd0923aa8ec40fa3215d54bf9adc426e855b5cda81f5f2ffb5d67603 264 \\x21ee9fbf5fe880141fc29ce330fbf5b3038a564844c7cf9ec455973d808aa9144971dd5e6ddd6378c8c745e429bd6fd9fe07b88dce91984141e46778bbb3ae6c3db6c259ababc2bed1a4e7a9955112c6368c545a31fd8809bed08e20192fb4efa768fc53d16b2574042d0b55c11c0480b8028ecf5112cef123a97c9d421f644f \\xa8e5eca24acc1e9bbe565b700d8580f34ca078a7587c9b4b998901108c7b354195b3288ce3650e9035a4ac56a8d4d5a71f179e526b9063a740717b287e3ab83e \\x00000001000000012e642cc9151d8fc344aa2be05b80dbd900ff389cc1c82fac4f345378b96e18dec73addfa54e902c3f65d10bfe7a1cb1b406c6be60c250124a3eead59fd58ec34bc561c18f2f1d7ffa9d91d6a892c7825c35e14a47db6471fb5f21d9680b23bcf49f5d9a48f8c0ba9224db3939435ffa2c58d802a2d4297a1c0ae0f7900be52ce
-22 2 9 \\x0b5a73ba8d2819fe1ca81a6652f7d1bc70fc37f9c2038ac6c33b0c2b597002e4e842e955d17511cda77bc28cbbd9662302cad7d0fcf539607d2caad321212f09 264 \\x9dbad097aaf21d3f8939aa3cf16dd0d5365c610af5d6e1b9b9447f341799df876b7f101e0f5839d117f7f27b1b01242d9ed3fc579c17a5916d2d6c41ffe7092becc4317a11522bb3d84613309fcaefde37a45b278d69aafa56d6eb8d9a30270cf52999ad38ed3c1b962f9784222c003257f5ff72232b3afd45973b1f55b52b66 \\x49676dbe51eb7d1baa971c83b0a04b70221a7a537c52677240458604c11958b078d9fa25b7abcaceecc7b2518c379395c6b57e3de34862792b6227d7f436adb1 \\x00000001000000011a3fbfe882ce4e34ace05195fa619dbe87dc250c03e23f57cfd34132ae587bc08ac2fc82ea31eb7122da19e2fc8118a20e4f45c9e6d0e2faa40136e329a784aec6990bb654baaa819b5c5fbd5366fac110390c6b789473a00339ee9aacc5773d8a8112ddedcdc9b707b510dfd91ad4f81fc9217e9e701ce7fb7b89ed911523a9
-23 2 10 \\xfd92aba81adff23c71aafdd3899391f7c799964c94ddeb1e6cda565f0e0ddbb0cc361497324509104a2dcc222ab6068ff433aa27bb859f977cfb84516d932003 264 \\x7ae09fe8a12e220437f324a56bd30363ec6a75c314c73d4ddfa280e661065eb65f5f2e9475fc404c1da4617b5794b841b32172513d69526ab2151553bbd28d3bd611b710d80688d5d200804882ad42679a0e5c3046d6c4c20fb4ba612af6ceb2f2b979c7964e99bea30152986f18c13e39e9bbeec414960dcb8b1dd5547ebb64 \\x7f59ee4d8f8d4261b1ed75f199e5e3a08eadd8b971054dcaa16fb2c5489f35f5756af1402ec1679f77c5e9ac4005abd466ba361de02a70d2ce16c97d34280bcf \\x00000001000000010856d097676fc94d950a7936b9416e6f4c891a4d97ed7375b23c29c07d078b7e4e7b69d98a2db9035b2e04b1faf129ff0583af552cb46289f2120e7f42397fbd373e0b165708d26cc21f3e26cd9ac3b3c70c5bda4045c0eabf358b2f34397a657721184719546392d2725333d274394911f31128f20914f9ae205fd0ca67769f
-24 2 11 \\x4b1f2f7b8bd05c8f431a585a6c05b62ad6fdbb328459e6391bc8c9a42ace4d0202a74ba6f7acd534d226634abb8ae47a47a3d37f600e1a544ba7841c85e3c70e 264 \\x0e6f838c34916e38ac45dbeb203bc1f2ed6f699c7cfff074f4760771566bc122bc32bee299eae1a21632e64bdeaacbb07999e86bc78c6c7f87c8a7eb26ce161716503520165b3b4933b7c658abc72975355a3c04404d05d2de70f3208700c4687528fccd4ca274510980e05c03de83554d3231470515814534f5b80c76665235 \\x8358e7041474dfab6f843448117ee602761ed8fcdc102eb06cb9342cd1d36197c823eb3f28c5f1f67b6bce4337d1c4630aac0e3c6177337cece99d5f21ec80a4 \\x00000001000000016f86d5af3aaaaeea3ff993e0417e05d56020e60166766445e13aa76685afc5c0b0f45954a5929b90e32596aca2b910137a95d60e37b374e80826462907e48da7cee20bacc19491e9d5975fc6f6d870330d551ba5f7dc5a3aecc158acd721006d52cfb894dbd65150515fcb05c152fbd2a6b7585e218b87f52412343a79a75732
-25 2 12 \\x02b3b77015c737a0c916b6f17afeb3574b75496759c4e13fe3bb427e465026f01ef20d0143484a785b6e75bc7a2e4152c6e58d3fe322301212a727d9d923b501 264 \\xbef81865eeec5d32ec3c44721d8c7c2994cb91ab544f41c832b8dd36dbea517d435f6c2d94cc79b8e3ab0e5953b9667fd18d4d493744ce5af0dccabd69e829a7cc643057a754a3aa3b31cffe0e94fe2bba366e66c881a2f6835d2a107c9fb623f00100cd36e3749cdbb06021a3567f140927d713da378f7b0df799d03b122067 \\xe4a24fa2a349b602d59567169d502c8889cd5cd4456f4fac79fac2d38be7cdf2ee4ef6875afda1d08939705f3aed0e6c7dd328853632123b3a51429dc7fb6d30 \\x0000000100000001a57ae57b00699f37e9f4a49cd8e2c69174f63aa647aaa9e104bd4da20a6145a9d49a87bd4b368dd926f0ed0516bc9004c631858c3bd30411c16bdd4dadc7e64f99d6f4bbbb70160740074a2b39344dc276a760eda3373813c2e04878920299a5d86fc1abae255e6f6d269e69f5802eec3ae82dd027ece735e901177971ec04b0
-26 2 13 \\x49834e8827f71b186f0ff6f5c49e65c9c1336463acc9e25c0594c16f0e597e744f7248c16f890e31cd25c5d5ea2db89e6729eb9f5082a11ea4d6cacefd7a2a0c 264 \\x072ae67cd56ddc1bd46ac9290d8f5fc4020467d23e41008f79d541b6e698abcceb7cba0e5bb8463b7ff9e56fe7b23f297b2b107260675c626d37e40a7c31d9a3e47250f3f152f302901575506ed5e0345d5ce55585d31ad6cd8c244fb8d596c868d9039647ab388279e54c8de149eebc8767bccf44add33ad8d673cd10521ea6 \\x19f13563bd8a01c3bc429aa451f4e0330e155300d81b5c0b22e3a66aed09509e0049ec1c8b716403a9e518d4439e83c1b66ba271f82a7a9156c65436b14ac9f9 \\x000000010000000183d11323b86ce90818c899d4fd13757a95a30e24a3f6032a2d51c5608e5734b787e82fd0f52dac7dd950bb91bfbff2ae46fcdf405178015ca794ee65d6438f712a089ec97d2dad8d7e7fcf8997a520b75470b281ae01c541d65d8cb6153131f1c2f34e766762ddbaf43aca6833420d23f7d20f6889d8004c154ba6a8b62718a2
-27 2 14 \\xa183b1cc525db141919afa520a1cde9ac1fa2b369996f0943404d9d18e5ebb7d23f535153545a0a238ac9f175d08a860f1ae6f36cd203e06271276dcbde1160c 264 \\xb97a30aba26139a91a63dbb6a87cbba29433f546e529ae48601f6be15d4f634a41000467e509611d4a5025ce070eccd851844cd913e0208770e06156a0c96842dbe7ea40f793e7e41e6966f5cc2379314d81607fae56dbefb23532e52fec38174da57b369c9dd9e4b917064201dccaa99d2bc180d16ab2dd161cfc85dfe60226 \\x686f1715b29fcc677b24c0ec9f4cf15249fb004fc73f97b5817827ad8b023e503a25b2eb827b1455284a1445f646aef1aa0e8651f41839d7c84d12a1183b43c5 \\x0000000100000001813ab426e0d5ed6f60830628ee5132c5ad874a6ee40a628dfe4e3b7093a1a0be22da8896f02691f14f068ae9cb1c7b69833d31fd4f3d9b09c13cd14ae459af36b2f475249c87b52ee179fa6258c1bcbca1ad47c8f6d15c482097e1e37f75a774237c723f322a4f64268bf72ff85969fff432e9aad53ec5a3849f55ab03b8ad87
-28 2 15 \\xb7ca662746fde31ceba26a9a884744bbcb3ce3bc91338f0650dfef1e0d131500fa7e50053516d7aedd859460b63b3c050b265d3d21e8626ff3a32c9031a2b407 264 \\x30b81601ec6080f5e05f7cd67562f3fe9ce5687b5ad1d070a60c188d341bec921900fdd37624b1b98d13acdde325b95694c5a683b46d89421bd8826836dad1dc18d5b9a537ebe61d8fc949aab492af0c6deedc52baf8ff9f89bfd5643d94e1dc2d99721640eb1d50f62519c23240c87bdfb49c5261256c4528fb78c3e5a2f9d4 \\x9472b4da13d2f6109cc9a0efb9d6f668fb7188b91bd668f3119ee1efcb534cf56400e2f9e604b85b616242f6169c6d9edf35b298c708017108d950168afcdbca \\x0000000100000001a2a822a2343a24b09bda177813a8abc2bd5bce977e7c306b0dfc474bf613e95e3d76d05c5213892d88878397380b2eec39c14224a07cee126b69fd7c54acf418fe1afde82e811bad59ae6ee621bf233d387976219a77be8fdfacd9fbdbcea561b87d5991426655aa7c33c21b89faca1fe636c03e363133ca9e8ed33115914588
-29 2 16 \\x1fcb8f9d9b9deafc3c57d5fa8d4b0a2e31013cdf96ca293d3da097edc5eb905ddc9d2124407d05ebfeeffee44e5faecda19a00e181fe31c44883476496c0e00b 264 \\x4785401a0862d8b2e1a826a921f02bd99aafe4b49c774efdd291bd0aa8f5bd4c1234a71bfc7d3766b7f2b2b7240c3459b126f0ea268a06a492bdfad3a1c78dda9ceb3ce2c57eabebddec6e2b7f7868a03c38ea9fb960c445919f922ea77293244bb7807ca219a30d7667a8fc57ef8b0632d434fb65dc7ef669075ad32455353e \\x8af26b24d8b75e84e74563a86e4ed5b901bebf1c957ca948c99f84954716cb8446c6e7fa15513f7e0114e1e50c8d664762218740e2c1d2da9c26b2825fe8a5fe \\x00000001000000014ff0228750e3a387d155c146d62ae9ce3fc11be9dc17e6b50a3ee960a4a26bf4691216e536156ff8267029fd2dbe740674e4620e32638fc669078473f1000e15179a114f3fedb3988ba65cd03835eef247749ad3bd8f29ca1a66a82b5e8bb9395863946fe2b002007b04254f2e9c22ec93f7338ab28bbea682bd638aebf334d0
-30 2 17 \\xa62d41cebf78f147293da0b62bff7be8e8270eb4643de89ea8884e42d37764636506e776eaf15d06287719e98c9500c25cb8dcc16093bee5145f1a4006949a05 264 \\xa8be262e9dac9cc936e0e68b1bc0d2a639d87027ecb8777434a45c186f8d33d92594f5f958e3bfcbb76814e3148bf63a5648d684cd0d096c93da51359c631ae9228480a89a1665714680622170df475ddaf2deb81b6f8fdbbeeea187d12e77e58c6bb9191579a738ba9e78b62e8c8adda1ed2140effdf292908a44764321c0f3 \\x6195721d2a632d8ee8354d7093541f1d8257ae0662c192b226ebce85b149e82c7bd0a8da4f52e2da45e04163e341fe3be36b844695ea5151a62bad68a4c5b3fc \\x0000000100000001296325664fb139bc970b7460a13e24a0f919d2e4ec22fd0d8cfdb2d5ee7e8f09e5a6deeb1d8702d98e694fd898db97223e0523e015735bbae3f483e6f488e6289ed7a29cc29331c843442d29b71e80b80107bd7bdec5547c3666e1f30c7bdc0865b8a2f07105654ebf4d9d0405fe6bbba75642f659f54e3d7ee4c39a772fb502
-31 2 18 \\x5068ad0ae89dc8525cff4208001cd842d65a7578d2375e04bf793fc4a5ac272b454d0c3e4a16c91d5003eb08106f2b8d2fb45ca64e32559d2d3cb07bd2c55602 264 \\xaaaf39a04a7f3c6758f2f0fe7e669e27f2d9ab53293ec371db052b85b6090bcbd3b04b768036788eb99055c5e771c27912be6533e4e94c4a668e860850d7fbe42327dbed90937dd606e2653ef8c6785d3cf43f9e7a3ea1dbd599338e6aa8572f95c6803069141b6f0c4aef98ffc463febc6d8b9c6700f13619ab749d0a6e96fd \\xadf13ce5f7bff4a1232bd76755bc91969688a4e3cb9f8864fd878ca32468bf9d3b1b8039c7d75efd2500f98f193e817481deda71814905ee907cf024de0e26f0 \\x00000001000000012f946fd20a6fc7587c6372c7204cb68f06a801409d6afef21936535090c5b9b9bc77956a1949a386795d0ba9d039b65e0cf3eb3115211b27c0898639897c9a5ce9cc9a6f325769c5e10fc6687f0319036a32510dd4ce81b151ade6510f3d0f96d4536f7af42d549f1e87b3ddd1080c2103dd91ff11eb1ecaca2e3aad6ef02a3e
-32 2 19 \\xd115472ec4f38de85c5232649ab704ab2c25078a59db5282575e98c03f7239c564be3c41d7a5ad38663d14dd40563450b04f019c9072e3ed65457fb7b90de407 264 \\x61cb0e0e129ea6bae520c8dfe9650bd9dbd0234ab5af143b7ef9a319e84ad9af45a3d055f7a81648af9ea3bd5b3e1b99176fd38c136a6f31e1633aa334e57bca2f43b37a36c3cc87cd8f0b1b039f681c192c3e132fb54c811f16d2366795ba738476e13487e43ee78e5cc52809f00baa7eaef822d6a2d84da0aba48209fd13bd \\x08841aadeecb80099cfad8d6f075dd14f91a9cca32c9771b3634f2473b6b5fd45aeca98e1b44335909056f4203a857f2a5c44cea4a7cb2d0a3a5d6c9e18d0411 \\x000000010000000138e43f4091cb916f65ccb67df5f4ce9f021692723dcfd394dd0d875a87602d0744de0810c13d4a0459e61d2a8326fed6ee535aae8b06d1853a2d3f0f67bd884c3be7b548fe90d8aae1281aa6df6b77bdfe8263d6bea961757e4c2610d32ebb72deabba7276bd47f8fe9dc03bb1029e69cb5dfc2d849ab5bdbe80289210667685
-33 2 20 \\x5adbf7e75200520caaa5730f243b2867b0a68dce77e1c04f4ab166f670bbf14b3f02e56d0cb98ea7f55aa63134df61bb8daee67f366b2568495aaaeacbe4ab02 264 \\x06e9f3a11c49626257d85890718dc63765204b59c25da14c77b37f87aa8248516dfb040a3e25751c289ab65f0f12a541d138b86183aa989cf59ea20447ae8f66d08042c1163db6f6afe69d271c44f2e0d01a656e1f68aa56c6daeae4d9861a76465ad5e1c9da571e03412e890744f09b13b811e035cfde1419a083a6a0c49e84 \\x775b81bfec7edf5841d08ec9990e3c132b23713f5503d8a1399371df858301beff4d67938d283ffcf82dd696a55131d1a2bb16c7b0427acd2ca1731735848653 \\x00000001000000010ac6a1987653cb085987c9b8d21b1da9cb89ddd3d711c71d7092da5f1fcb7ec9a66057348ee291b72ca5f5d975a9489dc21ee3a917ae7012775434bb5a20be9d7f82cea15114adb1f19048baed2c95d937a914aaf7a6214e40aacfc1b07dd48f44cd2ec2ab8fffae408670b2e66ec71b304bdd75f1610d20394edacf98debdd9
-34 2 21 \\xac83027d7ad2c45a16e1521cae8af9d11d78a6da90be829e1da663ade3a7668ca83812557fab8db5c260c47288a2b699ba6bfedb6c28b82fd848eedac7ec110b 264 \\xc1a30c8ddf044f1a9c411ef9225c8edecf6e68cec1b47a9d60da744c9b29e9a591900d077427f12fe03453117912974928a09d0c3bddf8d1bd2c042018993fd3e97919e585bdd0c81434d472710e4787a7c48e85ebe85fcd69dd4ba6f26d302cc669f0b4489693eff040da525f30f26a254c6056f0662c59af17e24f41926acc \\xbafaa6bd023431ff3fabcc17ea1148fbcfe0bbf6f8b2d4a7c33d6a0e8cfd83227ef28f65929f3bf3e487c7016887c8b4a53dd428cb62c518732769ef02f48e06 \\x0000000100000001124b4df367e3f5e291b216048c5367c9e94eb570605c51040f23901743ccff5633d267dca6a6c2a7b0cc8db5ba559818dfefa1a2ba985821d1fadb6fa689658597bbcedc2f54513b09b668d7a1d9dc474ca149c1b4040ea19cc3f8941c36efbc963b2d234913158b6a8cba99a6577106967f8fe2f861e9c7a41847975df719b7
-35 2 22 \\x035a3f3d59a34001fb94b5fe2511a0cd19fa058bf48c95e0fcbaba66a7844db7d0b4049759e1b26240d3bbbcc64619a76cfa552c5db2317a16be43ae85f8d301 264 \\x8909a97e91d79b0c6f196942eb59117586b12ffac5697ef15d732aa03f935b6b349a70b6399b5645e10791f91738de2984a3f4be2d2a78cc56ba06f6888ea2ffc0b5540b08b1f26702dd12c82e08895e2025904de2a7e607b4a09626b7f3c3cd113fb72fc9768fd6577f477aa1e0fb1c22082ace3ea82f97f8c63179d49c4fef \\xb4359af06a85f08d9abe4697758543fe9a921ad89ae91aeb1a53949436c145d8fc1260baf64ff5123f39ce116b65995ad9a5e021ee07b83b344190732497a459 \\x00000001000000016ee16bc9001312dc72de0ffef2027c4a78f1e63ae7b1188ac257c0161f5dae93a21a829fc8e0237b5ffe892f98be0659b040a20303802ad380883051626f3a1efd25148d7dfd4cb98963e6aafef837f49dce0ee112716684e18e50c3b39083c39676b8cb24eb27fe09a0cf3cb0db2fb73f080c44960db31987fa3f633d3b8d07
-36 2 23 \\x98b996a58c5207adce6b5e3f37efb9f08d0eccf254eecb7496dcde1f566fdf6b25fcc671111418737e5b510d0201bc450ddf50b4b253217118adeceeb9814d03 264 \\x228cc70198f2f61a4a1d35fb6c9c9b0c88b65be290d3e103cbf40a573ddf055b14531f752ac481d03148feeeceb46940ebb3ca6d1750974b3afdd9ae9e3881966ed4839155f083457acf94f0c6870352c7efe34796a392de6353b6afdc1924e5efb5be9e3e32dd328e92cab3ee7b584901d5ab6206e2114bebde6046cd4cab48 \\xc47324b605dc1324f0e2380497555d138e62afc43f4d2d243b410b2e1ad0f14de92e327b24babf497d4e89c1a456d99f2191455523f1d0ab9fcf3b0e5d269750 \\x00000001000000010f2f318875cb3d8fed3d67a1e2e7466811bf683475813e3a0186efa877d5fe7e77c4083cefe5ea1079a46f618977b5e6ed74883072d80ae2129ddbd78cb081fc009490915717d48b46f38f108d1735bbf405a84fcdef4217b2e225ddc68e4cb22f91a80de584ab58ffaaab0a2faf093dde13c6d41f05f6ae31fbd834c4f74589
-37 2 24 \\xbf1c7151ad4661556b1e492c0d0833d549483f0436a2dd0b3edb0c33061a55ec740ccc695a3501f0bf3a33feb73c2d5fea6275bcd883e4e2aa95b31eb92e9208 264 \\x348e5f23b69ecac984cb5117c6df7aa114926757a1d75559e821b70cad4f2548b3e456909403b20582c2048d4a864797c4e89e7b8ec7e38c6ec76fef6256b86a95d22f849ab08b4df1e5db02a6fdf0a5981b5e75023fb0bb271267994cb61948cf4c6a192209e6d553feaed0497eaaa20b96d50b7d7703673866018eda53590f \\x6c6b783599442a4f70098d8278a9e7b82c05253b23a9d69ef45b4b93f0f2ff6e6d7568f26fc56753274c0cabc05ac1bece8430f218f2ed13d5e14bf7a4b3303f \\x000000010000000173c6e3902d815d80366e8b111288fd4eeb18f76324339fd50577781b6d5dd589ac3c99722c0eab396e3bc1b7bfba0d20d85a2dfc723309e16d8c82e1e845555c51b8ecc4334362d17b7a6c496d20332521845bc1fff3e92adce7883695cc3ed9b5cb73a42cf3aa4de2cf8b130ae1a49ef04b250c53cd00dcb6d94e54b17feac1
-38 2 25 \\xae9a04edb11464e50f090d908d1076c70d7f395f54b9de61a1d5706e463b606dc5d7d4d3e46f87414462afe7dfa1c1a8f0d8d2c953d86ee2ffe6e55b0e335408 264 \\x30e18900390e6b8a2b060131510a7cb6f072f30a1b55a0034861e0f819644475959840a7c4cd01d9d78de3aaeecdacc23ddcd45062fe3e07d6c509dda2d4668bfaf7f3ebba8a56bd032d92f3b30d814f10821445dc364636a80148688e0d728337882f9ff140f46dfe2e92d15ba69a0cac1f225ed4e1d5e221b9467edcd9dd01 \\x84bddf115e18f9100c8b50dab77c8254ec70eacdcb84c0ebd5c610d8cce4c4b4b09d80b13059048e175c5e45d3a61acc7a02e1684d684c523b849141e3bd2b69 \\x000000010000000154e9d88ee94fc84165a926ce002020b4d80446c8167eec749d48632ebe35bd05f01a139ada1d949a080c5e691451b2b811489884ce1acb4255e53c0753abf3151e781829ba2a5906d45078a89ab81c134815c32ed3c46b74156e56b6fd3ad010a9cd3ca9a5f1a8de97e78e2e2427fbba6441be9e9f16a5ab8d06607f74ebdeff
-39 2 26 \\x1334e9d8829ed93c142c30bba8e469e3d94aa0b2b2cb3d5238025c1e379bb2cd00f8584a7f00aa5e962496024d497c55d24cbce3f22bcf0e441d42d06731b906 264 \\x611f900fb2fb36795a16afd6bab1365451fb2ed1e5220db6540e1a7aa1a6fe8098b568f011df2dfdb3b1c4d53df0aeda4510614bbec35c3b08c7f5d78e3f618390c970745f41db8e5cd03a424b06d3469e06706fd7eecc71764aaf3ba20c87a5924f99374fa74b266558f87234c7fc207765c4a24ad46303140a9a4298a0c4c2 \\x0b6988bb84bd0bdf5874cd5489d41911c26e4aad6944cea723b9369d40a7ccfa146c5499795f5f15d16b2d551e957ebdbf5845d1f6278c4c7542f2a188509c79 \\x00000001000000017b6b47e60bce706d37ff51443496518a5764054832f2fb371333e2b766354a97a49f2bfc82409d929015d52a1f42f7d02d0a3abeacf5c1e704c78826c41c29891834cedaf4376889aff893fc18050aae2f7006cb8ad96d82f3eb1b059f1a2845405294ec0e4fbb7881e772a0270e4b4dc2eab79fbf3a36af20fe19d0011bb673
-40 2 27 \\xbf1a5c4e0b6b6a20a374b959055362767392b38bae35354060fdcaac878751cbf1a4434d972ef94952db51e338fafa7f6d94de6fb73d0c106ea530451f779b0f 264 \\x30aa2673a9eeced79879de6f6826559ec18104423eff556b7b609d600e373f372e42f4a7d2a300e4d910f91f2c4395afe5ff995240c84222170eb548f04c00f785cc9b633f03c022091248613a5d520a713b913dbc025dc4e595ce2aba8b3810c7ed90276db977b7ad28058a90b4f96fb4293a42ba8ca5e1002f45c3e7cfa8d8 \\xe9d870fa4340f332fdb0c93ea5a9edaaaa2f13731baa4f6c5452c440c54b59b664bc1453c80e59541628016e254f476639e88576fe20e9be2c7f58b201e4b5f5 \\x0000000100000001aba8c847541022b46419faf7408e532d16267edd5182ba8ceced57186f7a78741229bf11990ce57e2719b264955bd2d8b1bf3186b48941f586bb6c5d5f6befc74eae5cd1c46cb62beebcf9fdd3b12306bb9004b2f1bda71fa8349d5ac5a56739702dca57ec704d9dbfb46957196584b1b9958eaca967a15ccb279b6d03a1f800
-41 2 28 \\x44ce310f9ba632c80374d2a0a259798e7fb312d85c3813e9874796f708921f69ad3fc898dd16e5dd0b3171e9de679ff2aecd8ae9a8b3d47bd1d15ff3b6ba620b 264 \\x11355c3c5f2863c76ff2398a65ea43bba70935db44cd29e1c4e15a7f0c733d86684f073bc900bb066ba824c0947b5b44529b3f78047687a288d46e90ea00fca3ae6facd124755f2aacfa2dad3902cbe1d0376db0783f46e001e53fdaf630bdf6fa1ab800399104ceff83f4c312f5523242b9152856391b39b255d9c15b81687c \\x69501ce4067327a88a40676d160b5aa3ee3451cc4cffb65707346b849b44c0e85fdb693e86edb0485c34c393bee3042e0652023a6b1da2cccec889aa0041b794 \\x00000001000000017be83730bdcd60e5f2540cb8e4c8b164c607a97b40c4eb446f3070588b0c02e5d3053612a09c04092a022458a3343674d599ae65a7a20f1fc0fd1e2f976e93d2478df6517a94b0e1ffe9e4e056e9d8bf5f7fe0bde3c8a44ff3469ca36cb1917d802373a6e141191188691080b1e9986602daf7d602e97523ffce53843abdf36c
-42 2 29 \\x3a3e5a84c16b13af76e500d839ed1037c59282875863ab731224e93e200b1c1dc1ecc8f5d830365ac17b723a25cd0bc4b4f1ebc669454626b3676ea9d2ba9d0e 264 \\x7c7efda399ed871156ec80a3ea69e914df5438e2ce7ccccad5866c8ee4d159b85aa8bc97a6dcb3cab492caeb4450eac8784a341124b8a1c543d77b90fd517a25e0583ee38efd9d8a87497096cf72d64165e525375ac905a0c7415897d3db55a67c564905fea492047a5dcccafd9213d9052ce5e9555868ee76e0b16b3cde99ae \\x1f33cb7480bb33b9b51476005ad70f90f85888dac7ff6ea131afea9a2191465df75e9d8121c9d5c7834eeb68310de3545e0c290170749c456e40341d7b8079ad \\x000000010000000153bc1c3f8de50454baca08fe39ddd47384d44b7c68d5dc97c7b6680d0ae1a0af4f9f8cb04aa7a04e558e20684db4bd489615eb0dde0929a49df37e43096f1733149fc00cd4f486e927a91f6f7162ef207913f46b6e77b91faefc84ee85a6ca2d200576800b4ac20aa24b6cf39ad610ce5cbb0d54e8f052b652dd997e9841311c
-43 2 30 \\x2f0e5f0454e79b2cfc6da7c3530ae95c4d487edeac293ee540ce56fa655012a4e68a24b6c342ea46a714d7b85150d4a3b0780fa84e9641220a31bfdc43f1e10a 264 \\x4d1fd3de1b4cedee8654234e8ad7c552f42f482ba467c63a6ec6cf4fef0e2dcf7b311e6d03306d823a1ef8cde7e3250d022ec7980293b21c633cf17f72607794130150ebff798d0e4a7db0bd0ec913f686f3c9f27377a5bc8ea225746122a74fcb236654d566a376486749c16e3f9fb934a6b7a269f4fb714054b504f553b5c6 \\x95287bd29182eddb23c7d4087110cb71c1887fdd3b32a5163d6b138420f714487fc3a1f508c61ade71881710f82fb3d4deed951c90663f593b63b8cb7b4fd08a \\x000000010000000118ca2310bb7ca7554f1e93aa65034df958cd2989dcfd22324c4d25eb7802c0e8067ea44c335e8933fe960d8c59ce18e7779f594d87ab1c709fec270e382724196d8d4db2431a648a6ebc61c288110d96be64b2082fcbbf4f0cb66c630582a243ba0893db0f8880af2b982b8c5c60f5336e8377d0e3770823aa0cea3cc7ca909d
-44 2 31 \\xee15a0572cb883c71b45ee68b9f6119ab23061c491ace08db5aee0c033ab9c55bc45fd4cec46cfa0690693523c44773c22c188abad60e7b5a7580680ce8bf60f 264 \\x044b77f67158f3c63eca2692a69743b4ee2c241e1897da718f27703ea053e720ef98a1a15cc7a324efcb8b3c0f2c272b1701206d68484c52cd54422aa7445f4e10a3cd779a343ec47f0b2b2c947786a9ab76fe23d0f126cbe758285980eaf73d273a7884c030ff5a94e07f3fe162f51a988c6420ba467c4f1cd81baa13af6d12 \\x5808728b076b8fe1b31d84105a29b313e2d2677090acc2d8976bb12b20e39464598d3cf336a82e4177e782bbdbd10625ffa8cb1f90746b43539520f12bd051c2 \\x000000010000000167cb53191573ef3a3ee8b97c07623bf8e1854263fc20dd64764472e3f9e713e3541407a0e4a6b6dbb2153f4154a796d885078acbf04734d1e27fe33084b746697f69a3efc6b814cb4f9c5a0b9e7cbc6bfc9599f590c645767de33eba5ffc474e0b859a144fa92bfd3c1fbc76cc8d6a3e827f669b14fc6cd2b1a5e08e6aecda17
-45 2 32 \\x4ba4f0a27242006c35ad59394747574a345d4467ad702f607241f849908bf2955ba486b4c3ed0fc3bca7a048173f7fc6b2fd80bfa5b77fbc18023f65153c6a0b 264 \\x4af883e3c9a0be22fb029074f827c808d12b98b561db3cb1a5ad74d3ca969af0640f07882b8fa44d499024e9126aba0ac5efe092e6d3b2adc95eaac66d193a33a270fcf58711e717c4e4f14df615d2b6aa2e94f6208020f641b1582aba0da01254eadc13958fc099d25d2527ea18cd964a1d057678b2f8a7553dd3cf9e94d573 \\x4155e854e5f2007164acbe3541b379f282d88d2481eb71683da55efb0948f479fe1fadd7404f39a1de0315f54d4031555e770d7a9b4cab6af05799dc9d48e512 \\x00000001000000017ebf3683024478512cca1e7227df1e5e58fe38019a23c6dd2a21668da682c266603fe4311cdc941bdec2cad11a3584d0d1fd82ff5a857f237e6ec0d1463a23ab30c2e067108852d7415e7686b7ad236a9779ec13f7e33b700dccc44fd8f8d5e3b868b4ee4e35d4d95020665311bc2e7b3d61edbb3414f56a846a677a3efaeca0
-46 2 33 \\xff45a95251b6ad55304496bbe4181851f234cd72861069f4451b6d271c4e218b15d5e2a46d19b470a2384666ff52892e48258de0d446713651ce35e8449c590e 264 \\x3f4d00d275496d09d684f46a078d42d7edc1a0b40bcb1c2796d65811daba7d550c627801db004d2aabf56678e6f755254d37736e7d6db85b62fefa9c168a6013ecdf171951a2e64e8155c52b8fed02923239886577dc892b64e163511e94c821f148fb22973e92e136f402be5cf28859baba2719b998eb9e654ab21a6b5ed61c \\x25de4e2370daad51493659b866d077e3650d4628b322cf3c28111b62f7712ec722915972b9e40f5b417fbfb28c567da8d3923c1eb5e3efeb3e8e983bbbd1c277 \\x0000000100000001924fa88f3bf3eb7cac1156196b0eff71b1f4996da58f30b66475a361aec797238bad20f566170c29f58a5b2616dbd1036122b608266aa4501418d96302d896f22605309bea2b3c0ecf85a4cf94452892432a1fb3c6d24631dadb43fb094d48335dcb414a74e03b880c054895feafe0e64ae67432b532261133cbcd57175dbf62
-47 2 34 \\xbec718eec0650f486bb27fa753aad0732c97801406e396e493b0b292fd6803eba49aa75f80824f34b0bbb0c9a192a7fa697896803137df9714884cc7d65ecc0e 264 \\x44f271da45f1f87089d86abe77aea9d77ecb79d9dc2de3e2b5dd9b501736af74b5dc70435072f72f05ac9ecc3dd89ee9b31ecf9b08e4d9170875a13c659862ac121c82d81f3b20ff00f001142c02484633ce3d39fa6a4d89226c8cc1b3a060d3c23de2785e14e9d968d77bc0f0a038662ea9a069df83665f7f2c141685aac7e4 \\x7f45e69d97c57b7a08a0c0359de6bdc2c49ed0f3b7e0b6def9d58e4b9ca5c6f86229b1079ad3720eb416fd116eb9f6e33655660796f8f8ed86fad41861046ec7 \\x00000001000000015e2bdd55aa3b8f125601f474a1628f5bdf81c93756b151ae9d924dfe3029c97d0e03185d91e924fe8c1dd13b43fe09d317a6167dc1d140ffdc8128cd926d69e5b5b0cd4648d6073a4a7dfc1afcdfbefc1feaf977a0f315f9934b6301b4c66399784d4e4122a0866d762c37f461d47c9cd8fa477f6f0e9ea174163284866038e8
-48 2 35 \\xc175551cdfc5d5bff90850ed5ea8cee4dc2621f0ae34eb481b2a7d2388dd8fcd251616efe5f2c682019923608db721f0e5320c3cc6b4db470a09832e3d7f090c 264 \\x4d3e181de332c453ecc705cd6a8dc44d80d8b2fdf6dec876cb902409d2ff76bde6c2ddcc45cd5ac5f572c24c94e918b323366773756279b4325be503c08734f4917cf50b079b64bebc029fbc6a7abeba5bc0f5880e964bbced84ed191313f46bb7d571e69d85cd9fd485513f0b3a479f4935cd5362a083b547378d4bca1f68aa \\x1498e11af9e12bc0451dc5e1bf37c0e2bfe536a024ef9b9b6396c1d7a7eeecb414be309b5e6b073355533939559826edad9c51480ba8befe0ccfb48688ff0fce \\x00000001000000010d2d0047ae1e5bcd2de62ddf46ea4630b8cc87e317a553e7613bd0eae057c531bc5362f3b005e0dcaa059c083c1109614422db2a23095b68eb426187abac99f809ec991fc879f99b53f18ac6e78b572adc107476a26854f67fcc1e27c51713c0dfd5afd33938190cf90580b2bf629315b6c477eb06cc8566c368ce92891dd863
-49 2 36 \\x7cef4150f4d63c21872f04fd7d37240ed8d60db384c02213677f7aa1d5fe138bcb79f559da6e032e65f3a5cf91cab5ebeee259567fad5766851e403a053fdf0b 264 \\x7f4ac29ad1b5349e41deb216a83f11e2d6119c50737f1857d9aa3df045eff0b4f51b355f98162fb2fa962158e5f22ca31d1129c47504fd3b6316b9dfc083633d486f39f54733f59cf76751c966e173089df30754db18a375070f57f41464f2a6e189da7d9bbd6a244cc3d55c018572c4f691b6990d8f918c6f508849d075297c \\xa919302ddfd6dd8aa5e22dba2c10b19bd217236d17ca16e29672d83f1c5344e27a0e3788f48a1829a28d9a2da12cd1df55d9da8ad3b91ab39455eb483ac95ac5 \\x0000000100000001b6c8fe613a6f0f2a44ccf672145d815fa5c591cbc7ae99b2dffc8757cdd13a4bdd3be68e19ea8a1a3ef39ae63561fb104bb3630946d6e01cd5293d372b5aaf353dfa919c2efd1d9db867931d85e3cd2c4e97b81ea06c0ea170ad40163b4f11adc1f82ac6d722db445f1cec1682e16a1a9cab69ca6efa08156004bd0728a7c8a2
-50 2 37 \\x23ad840913cd7e3fc37d1d7b4bf2a9f1613bfe1058d1f59a3fe7052edbbb95d3171ec821de95b9a4f3e8427d3ea895f396c35d0ab2d55a2f530779f396666908 264 \\x2db183f7677e76193a4b5ea5e6000ddaff1873939a7ce60484fe3db609b3b727ef5f4fd5dd6c2388c208443d1959b03fa0db296469f52dcbf5d2f06a37cce0c25a8c837d6cc53de4e56680e609b2c1c602adbd78c3290688a2a1a45a134ea55e28b1c03adec25b6fd5432cf2a3529c24e2b29d8692636c741b8c645e2dd3fb84 \\xdbd11535c6c6396cdbfa76e7d5e6f75344f1cf15f68d1094f8d43f38dbcb664a9392d53949eb572836864568fb7d048635697e6cdbda71d92dc84ab75348b634 \\x0000000100000001518545dce779fd35e82fee2c0531ce5b19bb2c9a32a400aba6a76f9499a2195d90f15a3e52b84e545b9df46aeb92e8fe5f3d02c5a22bb30102ede6bee0ad39a154e16e4091523faefb64f3b7a13bd4b34fcd95c2ac70b96c3628e7833030265582e9818e3397d1cb069d28c256535e72fd42d70e57ca8b5f1801eb599d1b0785
-\.
-
-
---
--- Data for Name: refresh_transfer_keys; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refresh_transfer_keys (rtc_serial, melt_serial_id, transfer_pub, transfer_privs) FROM stdin;
-1 1 \\x160caea93a0af89d27e79fa785722096be5b06b618f765f481460c1bcfe70944 \\xad5ed6ad3ae7a922cc627d007acf84f2c1f2c2789e7b216358709202620f880389a4e1eaeffd9a9c85386d0dd9dfb7ecb8413b02e5721ea5634e580b6a089535
-2 2 \\xc97f39eb021e2bfe41c42aa6cf36197c3f7a43182c5781566d3a1c91c0aa5417 \\x82c9ccf4103794e8794c199dd47ecc072dee1a3f5ffc2492b349e65858e2ee3c1a3101041b766a485c9a0d29b21a0ede935121e10e06b72e2347a287857f1fc1
-\.
-
-
---
--- Data for Name: refunds; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.refunds (refund_serial_id, deposit_serial_id, merchant_sig, 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_uuid, reserve_pub, current_balance_val, current_balance_frac, expiration_date, gc_date) FROM stdin;
-1 \\x94f97286a448d956e59904f7dfef557b8dd6e47c7dc092f69bc1378c87ec7fe4 0 0 1640786169000000 1859118972000000
-\.
-
-
---
--- Data for Name: reserves_close; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.reserves_close (close_uuid, reserve_uuid, execution_date, wtid, wire_target_serial_id, 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_uuid, wire_reference, credit_val, credit_frac, wire_source_serial_id, exchange_account_section, execution_date) FROM stdin;
-1 1 2 8 0 1 exchange-account-1 1638366955000000
-\.
-
-
---
--- Data for Name: reserves_out; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.reserves_out (reserve_out_serial_id, h_blind_ev, denominations_serial, denom_sig, reserve_uuid, reserve_sig, execution_date, amount_with_fee_val, amount_with_fee_frac) FROM stdin;
-1 \\x6c8a5bca690f8676594b2a753666431bc647518281aa2ece5a543573945ad15db4c9e735831c2d4d29967a15d7304b56f292fb88c57d6a72621e19ada97a4552 93 \\x0000000100000001babc1030733eae14ad29b2f00e4c92b9ac4897aa6eb2e0322fee41c6a2f40bbaba55d2f021bc073f2fcb413efbeb596552092e843e2d3c37102c1a34e538f404c2744711911ba6434b8d6f909e75fdd7862619a138637c7977a663111deb6f9747ddd385279aa42b29ade5ceeeb30e7bddba93f476c073371bbeac5800064fd2 1 \\xe6d69246e0b0b065938373c75634944f9a78b2bb8357dda030cd87739b9b5f06197ca8b9ed4f45c866f09f00edbe4c7d0cbc657240a29bda7b4adc7953a92e0a 1638366958000000 5 1000000
-2 \\xb43e6b2ff516747e8392cd298a1ca485494d2d8117d54a7eabe9b1a16467af39b2086bc23c8d13a6c9b42b72c67952261df55fca0610cc53af9faf59b75df3fc 92 \\x00000001000000014f6895209252ccebad850275d9dde87e3ff727a65ba0bd5a05bb7564316766d14b3ce12046b02fe79e262f75d71a116757844a34545dd5408d4d491d2d2ca648e4109085d79f7a54861d8f761f1f984a01468f280028323455b5402bed50490c89d0ef7ef27b6bbb05ad130845093991ad5a59c9bf564c203a86624815223a61 1 \\x1cee98a79aa45ead1e5362dce5e3b65aab6450c407f9e7ec37b9471ccee2b2040f1f4bb06963dff1496ba32314ec1387af13aeb557006fcbe6023a08750c0b01 1638366958000000 2 3000000
-3 \\xe96202116dbcf76962d8d3202949111410c58366d34fd15a599be1ccc1391fbbd070bd3fa937a51de8bc2f0baf39178d5baf78609fee80fcf87a52a007d31b5f 110 \\x000000010000000152b3a203ac44494a0006b1144d73d60b0324f62fec5a1ade6235d3cc04cc083f7c97fba23fb5526da98d6865d8376f049ec28f5d06bb9667ccf906d7790de9742a8d2ee6fbafa3f4dc687753a2a7d73d0e4bcca1b250b22aa8d9da75fc8b3a1f1a3eefdee731bdd047586aa942a96c7f7d6634291855197955a7f0db6c037da0 1 \\x5f87eb3b506c708106080e6fe1e6d92ccbfe9eb5babad08175425fbe9f20c69088a5cf4b88a68791c01f03477eef728f06672ba90e160f2a49a45a6465ea630e 1638366958000000 0 11000000
-4 \\xc8b4f3a3067d3a0b72944bef56047c083c0b037e0727f7c151e4d6c6ee60c43caeec68e3aa686111de55c3d5c68d9beb2cd6951ee2c46af2404dfaacc2300fa3 110 \\x000000010000000142db2c6d21c5ea0306374db008e5611370f1ccec8fa8a1b17bebc54aa676dfd418a1f0d670d114903881b62c9d0d679b97ee2190d1c97ea7e2650cc8b0b672026e53efcb85f54c1d96fb3150c559624eb78a656ecd187970feb9af969afb980ef42ad2e26ec060125a8d947a4c5d00dce28058326cdea8bed5bbe7dc99ae9348 1 \\xef55393ce281f899d00d25869c9d6dd9a317354c099393c4d78a5f8abca2162caaa2b5548b3d8b3ec0f8c5f58d5c624846f6d8c8fc8b61259bb3cc9b175aa508 1638366958000000 0 11000000
-5 \\xa388d1ef3c76b105eb438b2deb0ac7a9fcf9a90d9ce91d33819f4510b8ee122166fd09d0837518c56c41e1216dba72bfe50fa86e969e98444a88fbb1b3f1c8bc 110 \\x00000001000000012b32a8e3bf690588455490ea845016ad911ba610bd0ad2ad92473f7e762efc7f61d6c78d0045b5a60372337a681ff858a6bf47ded423d00d84712112be1a8c53155aec8d2c4d36020a9740ffb75f138edb6d6740264486e4ee53e7049fa23c146753976cd67199a08a9ed19292b6da4654abfcbd18287d93266f430619761406 1 \\x6ae530d29f70e9fc65afb1d97895365a96b7fed82356e26fe51b313dc71dabeb923eda6134dc5fa345604bd46487e65439ac7b44f1793725523a4e02715c7800 1638366958000000 0 11000000
-6 \\x0cf47602e10010acbeb1bb3fe1d0639773212cc5756f215f0ccddfeb088e8ea60a6725fa8572490da9b506689f05ae71aa0510e19faff23cd81be69f82505fdc 110 \\x00000001000000019c569a66fe454dff2c5bbf38504ce2a6765bf4d979901b23addbf770db3774073dcd99a6f08ace44fc4776c983207d9dfab8c9f14497d37d6a18a00fbee5441b2f4f47f2b430ce5ecc2cca3c4fbf980ec44c4b94b81acf5e2c1c8b5a54d62a0af57570b4f0909b27d9a7cfd7a11806de4846067b35fda0359fefc9b1f617dcd6 1 \\x721e36730ad19653e483997c83c0d580a0509b96deecccda4cf075ef51571f70824a7c73cafe7d86efc9113a4ee1a55f30270c27cb4ebe6b17a18578e8ae6000 1638366958000000 0 11000000
-7 \\xdcdc7cb3476a04340b3e8404ea73b04cea740f29a3d95d677f3a6fb264865f5520f72312687f3dee35adc8d316e1e5c4823361b9ca092ee6aef4f65eb8bd3838 110 \\x000000010000000131c965c99cb7fe2a822271eca04ef199a17c4c88699ecf7c3c9718877b7af5c9c262b2404750b20b486c3b2f2b0317c071b000e0eeb968f49dcb39818354b3fb3343fc5aee9a6c5a27be2d284f0b46537b3583d398e28fd252ece60bba7229e3da6eda662ec1c720492d9c629a02e1fe6fe6ae4ae0ff684f82e4f6f3272612e0 1 \\x330b6d555089dd79b492fe9f74760eb0a199a575441ce3f37930051f7cd020287058794531cfd66dccf780586dc938a46182c1ac7b304787d6fb191593826f0a 1638366958000000 0 11000000
-8 \\xb301fe6e2308a170dcfbe7bd9aa68adcc0a73066a4a916a7cdd04a2442f3f907aa1386e33b64da853efea3bee6947e3acb7587c31a829f5039eebd2e3c7a94fc 110 \\x00000001000000012eaab3ccacba644bf3b400dc9fca35850a181a9274244134a0482a76ea53b3fc521abd02b1c01802c70f54e908f31cd6c26dfb412d8e380fa799699d895a51d622a4b88baa5115f291927ead11884457f5c178c97d228b314b5544edc69b504cea928c75fbfaa4a226a4f0298458a79f39e983e9a82948920406471f420780ee 1 \\xe6f4b588ce294350044853eacc4f2a561e909c07d58a8897fd5f995747d41829b167fc1dc86b0852c1b276d561cf2a42dbdd5d34b25a114771238077b28f4a06 1638366959000000 0 11000000
-9 \\xc57f3fcfbf2ddd8e874537ad79eb825c0c8cece998e888b6cf7bd8aed755d21691de86020f7c849267d95546d89cd7f164d85656bc3a85e90c1b133cadf76fe9 110 \\x00000001000000017358a38bccdf500856764e3288c5d011efd8dae56f3c6098177930a5a68da804bf5c74dc776ba27970ea8f8cd6eb58c8cc79587d670712ec25bbb26ff3c90723ac76ed61bf668c5643cc50b1e4f5c19855e342c623210fa9d88e273d69b7e55a297fd6f1a12a52ff939dc1848bd8f895d38e1260198ae8af901c55084f1d149d 1 \\x27eac3fb12b3a4e12634e9c82640edb853bd20bb891696202242e8843ba5a1b114537b1b3eb02456b4585143247294f6131e3b25867e7a48e7a25f49750ce007 1638366959000000 0 11000000
-10 \\x138e053c58270f0c2b45042f44b2217f974fef6c4e89bbedb9d797a05b542bbc4ca689890bbe8d2c585e994c7b926206e04273a189c2ba136a06ac24c446ab56 110 \\x00000001000000014b652885d17e00f735de7679786ee0d1044aea797e917d5373c58d91d3536c487c9117ac7e0a3ccdf5f3dc069fed958018345b6938830de7be2b788602cb55c5483738237ab4f9b03f41b750320cf56291f88ac2909c08825d9ee5b3febe922c9bc95f2fda23cee0c3d38a10ca8eb74622efbc69fe527b1ddad4643da8951a33 1 \\x4ce5d6245fe8ddc209e221df177b260a117e050788c8f88c34b5047ab2741931304429c5ad31a26e5acaa19d2f7ec59e69d7b06c329c3355067708d960186305 1638366959000000 0 11000000
-11 \\x213105126931691da8f4427ab9892bf149042e935ae860ef349c108bc1f328cbfe6f78da56b70ca16c9828051607bc1a82c9106df7cb014c7e61fb903ee3a601 202 \\x00000001000000017108612ada8fcfd900dedafedb7f7e2ccf40d9eb30f313d261ab559fd492732939d89e9ea5007dff425a6599df8f7d68e9fc35db9287875a1757c96133748fbc34eaa65c82995dbe1fa601b12fbc985f4e026344613d387cf0fac263cac4b01a53ffcbc6473408a6f72f8f098703dbd2f132885bb9e0d459a02667c4cfd38559 1 \\x317078bb27be1df002fd5815036ea36ad5d0dcf5cdc01120a73a70ebb4c3209b6dc3f8da2c5c0827b3d0559f3a4fe30a54cf81ce740bb6f6a7ee3fd7d4f6360f 1638366959000000 0 2000000
-12 \\xa88b59abfdf2e1a8bb9dbea733618dd7cefa3525d2e825dbb66e019b2e62726849cb669f0948642223fcf1698bba6afa6af773187c0b20071a05a614471a6149 202 \\x000000010000000139586de67f0069910c8378f5caa1873d2a7a0b162ecb24f58894a654087debb9ec328b39b69a480fa36d3f6e2ded2bb3461d90c42f7719510b8a7abd83b7be8b8f72b9979c7e65c01064697cce80802836e92b605a90e7f6b6c4a8f3e8f83f5cb3fd9ecbacf0a061359e65c527b89e67acb5a330edf7ddb5e62caa921ee68bde 1 \\x8fc8942bc26cc608677a97410b88f48b23f104a3829ca0c12d17febce41f9c886fbd1cb46fdfa82c895844fb26ddd02f968285790c02d146a79bc87f53c6290e 1638366959000000 0 2000000
-13 \\x3f938dfe207933260dd8204bd3a9c2733beeeea01ec1c0c6cbc66cc875b4434569bd00f987f86234014643603357b4f05b819c775a8ac2c1d70c05650cd2c760 202 \\x000000010000000190531d0de21309b8ff9cf33d68e8fa80fd493b9c32a3092a0c683710a27b06f81c92bd244ddbdf7c862a3d3004aa6f22c2c2fb79778ad26a9309c72808f62e367f4867972b7936f5d874b0dd3d019747ba008c7ede2b65bd9d1182c8bc1551925189ceda5761ccd9fde28262fcc4491441f01479cf08927872662923ae12d665 1 \\x38ab3b2b9d47e0f66f3a693febfd9f04e6ef76a5c194b822b91daeec8560e43680bdf25520e444a5b39e1c4ac3dda6df1442e9ebb5e7a8d16be00918176a3206 1638366959000000 0 2000000
-14 \\xd542051717a5a5b2d3613e69ef96b0e2de076817b0c478771473bb0a13df00d72234abe0b0b5827828ba8a8a8a053a51df0e9210d08f2823240613b3cbfdbd4b 202 \\x00000001000000016d33638f822ce5dd29ff699281e31909d51309722306c36d515046e0765de7448a4e337bf68202535f9b5815739506b37903038f8ee5993b34d19629e29a1552ed8a469c1b89138c1ab595231d52437dfef679d3405ccc84cc20c3de73279e2d4211071daa3eb50fa515243522c0beaa885a606b2ccf13391b3716015d73077a 1 \\xe06d0a37cdd4b804961508abe6d900d6c94bd3a91da738ea7010d03d21bd57919a263aba076ea94bdc49aee23b7a83b2f489a47e46dd935f3f30b48d6e83f80a 1638366959000000 0 2000000
-15 \\xabf954b0e90151e7cdd96ab2c1829053c86f7a09e436cc76fd5a378a5dd3a6e4a5f66683af56ecf07518474b3ef8ca0b352db0e56a445a946af826420739f4c6 134 \\x00000001000000017a65cb8cb7ed2b4cc13d4718c61bacadf5dee5102d1af073aa2f0610f885e1e736ad63d9a698f685d97f7383d1c2f82ae38fbd4d1bf24a85c2c540bf7eb4859b17e180f2611e7b20deac61331042bdbe7993402073c9108aa34145ecde8d09667c6d0d706700b18b7d7bc5a3892d127d3e0a79afb4fded8c0dfcd5d3b0223e09 1 \\x8e4a711f4ee3c18aad8dca336801f304c116ea0cdf8a9b6cb84eddea2d71d8913219c8883b74c674b6b0f30ca46c8c5c075528ed5ff93fcc95b5d992940a8702 1638366971000000 1 2000000
-16 \\x2b5534d87350ebde9146ba2deefed3a032eac6fee8ab3c4220c9a83d0d562ace4d4c5ba82bbac17eaaff540016915ed02a8320ad5c6ccdb60e51b2966508a0b4 110 \\x000000010000000128d0e74c5b51c32776ce8bf2111669dde790a2c7546ff9425baf1f078d4718a7d28e0e65743d99136ccfde35ee49df586095f1048ccb1e34562696e43cb76af580f4cc1cf74d933fc5913d800bf3518311fb78f9ebe9cabf8454668270d57db86f26b609d70820c550e78d1e8885dfbd0107f04c05e932d9793cf1ea7da90647 1 \\x6fe85bec303fc992643307219ec6bfbe22ba76555de9dc5e01473dace24b58641faec2d30a073bfb0df9b13b91b24b4d4c3fd894296c43b1de7b2a3e2416ea07 1638366971000000 0 11000000
-17 \\x0fc20f75e8a3c80265371d9af373640693a94b52e8e7a25dc60a830bf970c617ac0bbf28aa3411a9819627c966654431131d804926f28536e03b44263b1ab3b3 110 \\x0000000100000001509a8fda90a578e10d55e97f58330a7fa72d82f5cb13d06d515c0e6e3ef50bff2817647d5eeee975c2fd37f46cf1ad41609c957f9f12f00212a9ac487f3c48969ddc616ebcf686851726bcd27e8dbb43ed38b23d1a3eac211ce78b013c301f54c9a7cb4773adeec965297911669d76bae763bab99b3e4d30ae9049fbdfff28ca 1 \\xe76d782db2f18d2101a1849561ab0e6f6ddebc00e355e9808e3ad0b813c2aaf9223c577414cba2436a44a71ee29ecf35251b7160d47f7c26ce7b92c2b3ad6b0c 1638366971000000 0 11000000
-18 \\x58b9d1ea6a522da58cc1cd02b84c08d0d8476748f3c30bd3639285f8f392a7fae1063398ceea57e658a0d591a9c9e1cfa7a3d2013055611c8c3bf3bf81b02f3d 110 \\x00000001000000011ba56b23ceedf44a50bcc3ed2bc6cad4b329881a4debb3ca34593b3c5ba58b75c03f4839c01c9a15499da1393d4a67ce60d64da9ab9bd2ab2432ee1b04a16114c07311fde401ee289027b74d9b9b0980d647117c064c9a8e450b75dbffb37a9f559caca750eca79c4f2e6bb56ba3edc5c8550f6d073fff6e44034e59a2607485 1 \\x7604bc3f98992fc5a687851d5beec482c3657c37c2998459a3c659eada7121c9a6fb8a636a417b3266b3e69e179ab73d8428960a6a0f4b639446d2fc26bff008 1638366971000000 0 11000000
-19 \\x11cf36bf76e5a5330fa711e9b472f6f93fe04fd63e0ac70758d0830fd0d1c8df84c94a0484b00b66fc118671a97eca38bc91d4508f3e2fd79160be5f2de5629c 110 \\x0000000100000001393a0db15cd8510d61d9205f3dfad16f6689e9835e44efc117d645f4d48d45b1fbd441ca54fe0c647a951424c7af38d3c4d8d29173960a4d9e08292f43e6b3874ebecf0573c3104b5b8d170a4391d64b44a65cce7da540a112e77a9206e7c07286a4be476bc62e45aac2c34ac4f944cfa360a354a4ad40cb9ecf65c3bea6f1aa 1 \\x51b37540ac730bf411b4cf0a8a56a6ac4acaf9d07d13d24bd11a077b4de9349cb200a073d8519f5a41067fa35cf85540b9e1b595fad6c13ff2fbd12ade1d740c 1638366971000000 0 11000000
-20 \\x021bd092ee75ae5b673f51500f64b74a1fa3808523e0e9c096cde6079ee09625355d79e2aed89af0ef132ed1cff50b6132c05f631be1efa9f58a81e3a72b78f7 110 \\x0000000100000001a822d80c487c893e4528ab3748028cabd264c71e8ab05232c1dde0eccbdd172721183b22526e8a073cb83053f7032d2f1ceb3891a81722769945c918a1a04d90402a8d6fb4980e8dcad75d68fe79e53a7ff6531bca39fefa0f1f1f6891b918960c2702e9c1f1b00e4bd00cfa67a7909d6675809e61f5d1811913144464dc9c3c 1 \\x0743f7536e839497efdbb1d39139ad359096bf3e521b2ecffe7688db9e4231b774d08d400824c8bc142b666abd0c548fadc8e5f2cb7c9172987ded5fd0cf7b03 1638366971000000 0 11000000
-21 \\x8804fb1f0e5845cf010a72de7c15862bd6f80e7c3f4de3360698a477b95bfea389c42ca41d2536045ad9fa22032643e985149d8c3adf9c85053da1fd091046d3 110 \\x0000000100000001aae5707b49f84f1a69595d3a383d1f9e8e22892c6b321d8548ad3e4276428cb3f32d0c0c0b99699e2387f0a02b8787cf207dbe521407ccf76f1cf70ef08a5ebeffb64b39bbbfb69524d9e905e6766554ad4e9fccef0264a80783bb8315a1a5b86c0f3bedecc629e6ff6e6c27cfd8ee237fc89df3824e271c558d28b80e480c7a 1 \\x610b9bf4849180e4f590ea66693efc6a0e3d60e5a9a242f240f96dc4fdc2198c0da89ab1efb8bf6a0be9d3525ff8e42e9d5c8c95317ff0cf16e79c50c0844205 1638366971000000 0 11000000
-22 \\xf02b8bf8798f8164145f66cb7c7650a6320446ca9cf4816c33dea70449117346be81ff937d85e640a3f5f956f35289dee86f35ad0b608ff062c4f23474cad11b 110 \\x00000001000000014c86d79448fa4c2c331aad1e5698d0eedf4358f9d234dabda7a72a9cf2ceb92eb5f08476ce117effdda7222ba0ecb5194522073802a2a023dad47604ba610d5bd0aba9fadf31e98900afc313530a071352c6d5dce5dfd6ea13df11e60ac20b4732b3e93e521039fecf23f145e036bb278ed321fd86807bc7aa28f74b6de58f8f 1 \\xbe0761cd374628d8430e2c5414f473ec725087fb1c3fddf30ac38a7da1cb75c679fce669ec56a31307533e2061f95df8da929f9b99be937dda0965118534040a 1638366971000000 0 11000000
-23 \\x5951a7b5587004fcb84f13f5db6217333aa0528aa911e1a7be8f60cb5ea7b26938ba83e7d786d837ce461b634a62fbb17747c065677ee093676c32398fc2de9f 110 \\x00000001000000014d1f8afff8d3d310b91cc2d1f99ab93de096f1dbdc682d02a8cfe31b2a266bbbf75801d916e8cb43538630c0742561784e0d702937d9c697c668ffc5d856796bc512de4fb86eab36d6d67ab9ea405f5dbf4360a007ec6d95c4c347438605da25e77731eb7e185612bc6b66cbfb0d37b5d8019bf5d145281dfa8c88b5e0de3739 1 \\xe978b5c3e6260ca7943bee0699fa539e9456cf334ead14296dc5f27fbda39f716aaef987007d0b7a47bba29525d5ab7a5e1d1ffae700e5ef28fe0919e0d01409 1638366971000000 0 11000000
-24 \\x95ec71021ffbd83b7392fdc1d9f45876fd811f8a212e9a36c96b3efb802e02ff11bced7adab5a219c0cdc724718915d80a3bfc2804aa5f41b0864f838286f06c 202 \\x0000000100000001a6b740dfb9ec35a0ded3ce5837c0c8b18f1883d0d3f300396701b46e6dfb16592d0a633d5a0972e758825f7102ebcf4833efea8469328fc8855d2e4fc29c323b26fd16eeb3e0597e28e1c0bc3802d793c49a16dc7d06e77870121e039fe40b3ef3f410bb15ebb37d114a626f73cbb7023f3518535518dfbdf8f1370b54757d99 1 \\xcd1dcebdf90fb78447d8227fcaa9b1f1fab9dbb582b5fc4329f366adff209f5eb183bcb3311c1045ad5b01aaa4b13f3f500b895c0ea9cb9b9a87f55a15caae06 1638366971000000 0 2000000
-25 \\x23dd245d1fc428ed1c30fe433bdab2090b6c343ce168fe078ddcd95804bfa341d11a45d1e1d9854fe2dcf9488b488f3562388bf6e293d515aa25597181603403 202 \\x00000001000000019d1d82faa5548980b9b6c49707cb7d7a365d1dabd8bf07fd01736406f629175acb4377dcd255193ed190664d7af13ae0171b3bf4531b45dee8a0f6c30ee2cdf8891076ddf486e3c460b3d2427f698ac15173a680337a60664db900e1ae992e3169d224efcd848f259b1c6ed63420cc87c19bde968f6d0b0e1329129bfc329ec6 1 \\x685f7065b2a04ddd30616c3ded92227dfdabcd5eb0371295e840e435b945ff41f67797827157338f1b5d944eda4e854f32e867e8fc907c35621230e73d4df90e 1638366971000000 0 2000000
-26 \\xc242c224c89b04462540e5924c10d5f97b7db5758019da6b4ca7f1fa9e26ad0c1aa4153cb268ab1d6c3335075dbe10083f55af0e492f31287d5fa926ef47e116 202 \\x0000000100000001581512563b077b27184e234d8521c32f7cbe29042acdf9441a067683c2f866d13c185250603b9fffb77d626b59c22e178caf4098b95bbe39a766583670471f074f828dce728f2d2e10019b257542d092b68a24f957eeb66c37e2398a86ca648a2cf192149e25b8b3031ea0825a7a5f942e01b0ef65453759332e36221fa69682 1 \\x8e54e434df7e64b560e5cea86577b18af451d313148d98fbd2c991052e179f6ab15b01b0fc1502f921c5687f12bdaa2b6adf18f1f53dd224af46b2276afeaa0f 1638366971000000 0 2000000
-27 \\xf8e710ce0b7bf9f7507865d7a02ddd101193364996beec1c8aa66f13dd247b2f146b7e1a73a1838590d68ed76e7099facfe67da8a3c72d4d642375bd07cb70a2 202 \\x000000010000000112fbb17e02b5285d2a923b8611d978149057e5f5685da913a833a6a4fa7d0380592a235c591d06403598bc18bdec83265410a9c5438743de76c10bad108acffaf67c4ee124b5c747358452c61b823aec494fd5c7ae3ab1e786f6c03c4707c5ad5b9a5541499cff50b0fa4a78138b6d4b9ef4298c0b050889f0326bcde6cd905c 1 \\x424adfc0ad9de5105c044bbbd0c4b83be87cbd0e551e34219aa86786e0ad03cb7132296bca9d0d5b8b8eb820e05d9ab6ed41e17248634569f31919d5e0b5ef05 1638366972000000 0 2000000
-28 \\x8233331d8043891d5f7b6b454c4ca3a764bc9062b6251b59ca7d4a3843b999c2a3eb59b402ca498de4e22f6cc20464c8c6cf1ee40e7ce7daa0aba3adc7adc4ab 202 \\x000000010000000126406531aa8650cc26e478aa1f0ee61a8a65aacf01edc49fd6ef4561764f9deb0d60d135e01197ff0a232797217afbf125ae40cbf9704e1d806f5367c5ea036def7724698a00fb7b87f3fb2220d53449cce45dcdd871475f91ee6706d06e48b5962dcda5657df2f8dc58ac35ca32e3772a84c8185fd5f70b02c6e21fbd28105b 1 \\xa001577ce6b17eb1733bdfe3979c3c13e402381196b1653e3a2ae37132b3bae03c1c18f8581bafb312ecb1a8c87487887ecc0685bc49e80660679c8a70965c0d 1638366972000000 0 2000000
-\.
-
-
---
--- Data for Name: revolving_work_shards; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.revolving_work_shards (shard_serial_id, last_attempt, start_row, end_row, active, job_name) FROM stdin;
-\.
-
-
---
--- Data for Name: signkey_revocations; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.signkey_revocations (signkey_revocations_serial_id, esk_serial, master_sig) FROM stdin;
-\.
-
-
---
--- Data for Name: wire_accounts; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.wire_accounts (payto_uri, master_sig, is_active, last_change) FROM stdin;
-payto://x-taler-bank/localhost/Exchange \\x8a64f036b08e0efab21b86a7ad11ad43f86147ff2ef2344c6520793123b475e76e1f069414986ab7f00c24125732268c497e8514a9b23f962502afc286835c07 t 1638366947000000
-\.
-
-
---
--- 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_fee_serial, wire_method, start_date, end_date, wire_fee_val, wire_fee_frac, closing_fee_val, closing_fee_frac, master_sig) FROM stdin;
-1 x-taler-bank 1609459200000000 1640995200000000 0 1000000 0 1000000 \\x403ddcb1ac9ae4bca73e915a1c1ef3d901b0c2a0aa4dcdfdad12276f18cc5d438c4e64057b4e11c76f8d05a3b5d6dc56fed992e05f0d76c06e892680b9458408
-\.
-
-
---
--- Data for Name: wire_out; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.wire_out (wireout_uuid, execution_date, wtid_raw, wire_target_serial_id, exchange_account_section, amount_val, amount_frac) FROM stdin;
-\.
-
-
---
--- Data for Name: wire_targets; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.wire_targets (wire_target_serial_id, h_payto, payto_uri, kyc_ok, external_id) FROM stdin;
-1 \\xb40379f80360623e82237e306f5b52ef69a0a887a079ae54290b186f50b433109618d2eaeaf295659a376375cd6c1c2f95224954146495875dfc61e077f6b3b9 payto://x-taler-bank/localhost/testuser-kusufpjk f \N
-2 \\x1e8f31936b3cee8f8afd3aac9e38b5db42d45b721ffc4eb1e5b9ddaf1565660cb3a8ffd7e9e69c646815045edc179e5e7ea1ecd9584550d202ae951ebd572e98 payto://x-taler-bank/localhost/43 f \N
-\.
-
-
---
--- Data for Name: work_shards; Type: TABLE DATA; Schema: public; Owner: -
---
-
-COPY public.work_shards (shard_serial_id, last_attempt, start_row, end_row, completed, job_name) FROM stdin;
-1 1638366941158878 0 1024 f wirewatch-exchange-account-1
-\.
-
-
---
--- 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', 2, true);
-
-
---
--- Name: auditor_denom_sigs_auditor_denom_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auditor_denom_sigs_auditor_denom_serial_seq', 1269, 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: auditors_auditor_uuid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.auditors_auditor_uuid_seq', 1, true);
-
-
---
--- 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', 2, true);
-
-
---
--- Name: denominations_denominations_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.denominations_denominations_serial_seq', 424, true);
-
-
---
--- Name: deposit_confirmations_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.deposit_confirmations_serial_id_seq', 3, true);
-
-
---
--- 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', 22, true);
-
-
---
--- Name: exchange_sign_keys_esk_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.exchange_sign_keys_esk_serial_seq', 5, true);
-
-
---
--- Name: extension_details_extension_details_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.extension_details_extension_details_serial_id_seq', 1, false);
-
-
---
--- Name: known_coins_known_coin_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.known_coins_known_coin_id_seq', 13, true);
-
-
---
--- Name: merchant_accounts_account_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_accounts_account_serial_seq', 1, true);
-
-
---
--- Name: merchant_deposits_deposit_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_deposits_deposit_serial_seq', 3, true);
-
-
---
--- Name: merchant_exchange_signing_keys_signkey_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_exchange_signing_keys_signkey_serial_seq', 10, true);
-
-
---
--- Name: merchant_exchange_wire_fees_wirefee_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_exchange_wire_fees_wirefee_serial_seq', 2, true);
-
-
---
--- Name: merchant_instances_merchant_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_instances_merchant_serial_seq', 1, true);
-
-
---
--- Name: merchant_inventory_product_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_inventory_product_serial_seq', 1, false);
-
-
---
--- Name: merchant_kyc_kyc_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_kyc_kyc_serial_id_seq', 1, true);
-
-
---
--- Name: merchant_orders_order_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_orders_order_serial_seq', 2, true);
-
-
---
--- Name: merchant_refunds_refund_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_refunds_refund_serial_seq', 1, false);
-
-
---
--- Name: merchant_tip_pickups_pickup_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_tip_pickups_pickup_serial_seq', 1, false);
-
-
---
--- Name: merchant_tip_reserves_reserve_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_tip_reserves_reserve_serial_seq', 1, false);
-
-
---
--- Name: merchant_tips_tip_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_tips_tip_serial_seq', 1, false);
-
-
---
--- Name: merchant_transfers_credit_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.merchant_transfers_credit_serial_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', 2, true);
-
-
---
--- Name: refresh_revealed_coins_rrc_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.refresh_revealed_coins_rrc_serial_seq', 50, true);
-
-
---
--- Name: refresh_transfer_keys_rtc_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.refresh_transfer_keys_rtc_serial_seq', 2, 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: reserves_reserve_uuid_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.reserves_reserve_uuid_seq', 1, true);
-
-
---
--- Name: revolving_work_shards_shard_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.revolving_work_shards_shard_serial_id_seq', 1, false);
-
-
---
--- Name: signkey_revocations_signkey_revocations_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.signkey_revocations_signkey_revocations_serial_id_seq', 1, false);
-
-
---
--- Name: wire_fee_wire_fee_serial_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.wire_fee_wire_fee_serial_seq', 1, 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: wire_targets_wire_target_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.wire_targets_wire_target_serial_id_seq', 2, true);
-
-
---
--- Name: work_shards_shard_serial_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
---
-
-SELECT pg_catalog.setval('public.work_shards_shard_serial_id_seq', 1, true);
-
-
---
--- 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_denom_sigs auditor_denom_sigs_auditor_denom_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denom_sigs
- ADD CONSTRAINT auditor_denom_sigs_auditor_denom_serial_key UNIQUE (auditor_denom_serial);
-
-
---
--- Name: auditor_denom_sigs auditor_denom_sigs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denom_sigs
- ADD CONSTRAINT auditor_denom_sigs_pkey PRIMARY KEY (denominations_serial, auditor_uuid);
-
-
---
--- 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_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: auditors auditors_auditor_uuid_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditors
- ADD CONSTRAINT auditors_auditor_uuid_key UNIQUE (auditor_uuid);
-
-
---
--- Name: auditors auditors_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditors
- ADD CONSTRAINT auditors_pkey PRIMARY KEY (auditor_pub);
-
-
---
--- 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 (denominations_serial);
-
-
---
--- Name: denominations denominations_denominations_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denominations
- ADD CONSTRAINT denominations_denominations_serial_key UNIQUE (denominations_serial);
-
-
---
--- 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_known_coin_id_merchant_pub_h_contract_terms_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits
- ADD CONSTRAINT deposits_known_coin_id_merchant_pub_h_contract_terms_key UNIQUE (known_coin_id, 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_sign_keys exchange_sign_keys_esk_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.exchange_sign_keys
- ADD CONSTRAINT exchange_sign_keys_esk_serial_key UNIQUE (esk_serial);
-
-
---
--- Name: exchange_sign_keys exchange_sign_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.exchange_sign_keys
- ADD CONSTRAINT exchange_sign_keys_pkey PRIMARY KEY (exchange_pub);
-
-
---
--- Name: extension_details extension_details_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.extension_details
- ADD CONSTRAINT extension_details_pkey PRIMARY KEY (extension_details_serial_id);
-
-
---
--- Name: known_coins known_coins_known_coin_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.known_coins
- ADD CONSTRAINT known_coins_known_coin_id_key UNIQUE (known_coin_id);
-
-
---
--- 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_accounts merchant_accounts_h_wire_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_accounts
- ADD CONSTRAINT merchant_accounts_h_wire_key UNIQUE (h_wire);
-
-
---
--- Name: merchant_accounts merchant_accounts_merchant_serial_payto_uri_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_accounts
- ADD CONSTRAINT merchant_accounts_merchant_serial_payto_uri_key UNIQUE (merchant_serial, payto_uri);
-
-
---
--- Name: merchant_accounts merchant_accounts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_accounts
- ADD CONSTRAINT merchant_accounts_pkey PRIMARY KEY (account_serial);
-
-
---
--- Name: merchant_contract_terms merchant_contract_terms_merchant_serial_h_contract_terms_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_contract_terms
- ADD CONSTRAINT merchant_contract_terms_merchant_serial_h_contract_terms_key UNIQUE (merchant_serial, h_contract_terms);
-
-
---
--- Name: merchant_contract_terms merchant_contract_terms_merchant_serial_order_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_contract_terms
- ADD CONSTRAINT merchant_contract_terms_merchant_serial_order_id_key UNIQUE (merchant_serial, order_id);
-
-
---
--- 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_serial);
-
-
---
--- Name: merchant_deposit_to_transfer merchant_deposit_to_transfer_deposit_serial_credit_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposit_to_transfer
- ADD CONSTRAINT merchant_deposit_to_transfer_deposit_serial_credit_serial_key UNIQUE (deposit_serial, credit_serial);
-
-
---
--- Name: merchant_deposits merchant_deposits_order_serial_coin_pub_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits
- ADD CONSTRAINT merchant_deposits_order_serial_coin_pub_key UNIQUE (order_serial, coin_pub);
-
-
---
--- Name: merchant_deposits merchant_deposits_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits
- ADD CONSTRAINT merchant_deposits_pkey PRIMARY KEY (deposit_serial);
-
-
---
--- Name: merchant_exchange_signing_keys merchant_exchange_signing_key_exchange_pub_start_date_maste_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_exchange_signing_keys
- ADD CONSTRAINT merchant_exchange_signing_key_exchange_pub_start_date_maste_key UNIQUE (exchange_pub, start_date, master_pub);
-
-
---
--- Name: merchant_exchange_signing_keys merchant_exchange_signing_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_exchange_signing_keys
- ADD CONSTRAINT merchant_exchange_signing_keys_pkey PRIMARY KEY (signkey_serial);
-
-
---
--- Name: merchant_exchange_wire_fees merchant_exchange_wire_fees_master_pub_h_wire_method_start__key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_exchange_wire_fees
- ADD CONSTRAINT merchant_exchange_wire_fees_master_pub_h_wire_method_start__key UNIQUE (master_pub, h_wire_method, start_date);
-
-
---
--- Name: merchant_exchange_wire_fees merchant_exchange_wire_fees_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_exchange_wire_fees
- ADD CONSTRAINT merchant_exchange_wire_fees_pkey PRIMARY KEY (wirefee_serial);
-
-
---
--- Name: merchant_instances merchant_instances_merchant_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_instances
- ADD CONSTRAINT merchant_instances_merchant_id_key UNIQUE (merchant_id);
-
-
---
--- Name: merchant_instances merchant_instances_merchant_pub_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_instances
- ADD CONSTRAINT merchant_instances_merchant_pub_key UNIQUE (merchant_pub);
-
-
---
--- Name: merchant_instances merchant_instances_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_instances
- ADD CONSTRAINT merchant_instances_pkey PRIMARY KEY (merchant_serial);
-
-
---
--- Name: merchant_inventory merchant_inventory_merchant_serial_product_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_inventory
- ADD CONSTRAINT merchant_inventory_merchant_serial_product_id_key UNIQUE (merchant_serial, product_id);
-
-
---
--- Name: merchant_inventory merchant_inventory_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_inventory
- ADD CONSTRAINT merchant_inventory_pkey PRIMARY KEY (product_serial);
-
-
---
--- Name: merchant_keys merchant_keys_merchant_priv_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_keys
- ADD CONSTRAINT merchant_keys_merchant_priv_key UNIQUE (merchant_priv);
-
-
---
--- Name: merchant_keys merchant_keys_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_keys
- ADD CONSTRAINT merchant_keys_pkey PRIMARY KEY (merchant_serial);
-
-
---
--- Name: merchant_kyc merchant_kyc_kyc_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_kyc
- ADD CONSTRAINT merchant_kyc_kyc_serial_id_key UNIQUE (kyc_serial_id);
-
-
---
--- Name: merchant_kyc merchant_kyc_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_kyc
- ADD CONSTRAINT merchant_kyc_pkey PRIMARY KEY (account_serial, exchange_url);
-
-
---
--- Name: merchant_orders merchant_orders_merchant_serial_order_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_orders
- ADD CONSTRAINT merchant_orders_merchant_serial_order_id_key UNIQUE (merchant_serial, order_id);
-
-
---
--- 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_serial);
-
-
---
--- Name: merchant_refund_proofs merchant_refund_proofs_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refund_proofs
- ADD CONSTRAINT merchant_refund_proofs_pkey PRIMARY KEY (refund_serial);
-
-
---
--- Name: merchant_refunds merchant_refunds_order_serial_coin_pub_rtransaction_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refunds
- ADD CONSTRAINT merchant_refunds_order_serial_coin_pub_rtransaction_id_key UNIQUE (order_serial, coin_pub, rtransaction_id);
-
-
---
--- Name: merchant_refunds merchant_refunds_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refunds
- ADD CONSTRAINT merchant_refunds_pkey PRIMARY KEY (refund_serial);
-
-
---
--- Name: merchant_tip_pickup_signatures merchant_tip_pickup_signatures_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_pickup_signatures
- ADD CONSTRAINT merchant_tip_pickup_signatures_pkey PRIMARY KEY (pickup_serial, coin_offset);
-
-
---
--- Name: merchant_tip_pickups merchant_tip_pickups_pickup_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_pickups
- ADD CONSTRAINT merchant_tip_pickups_pickup_id_key UNIQUE (pickup_id);
-
-
---
--- 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_serial);
-
-
---
--- Name: merchant_tip_reserve_keys merchant_tip_reserve_keys_reserve_priv_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserve_keys
- ADD CONSTRAINT merchant_tip_reserve_keys_reserve_priv_key UNIQUE (reserve_priv);
-
-
---
--- Name: merchant_tip_reserve_keys merchant_tip_reserve_keys_reserve_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserve_keys
- ADD CONSTRAINT merchant_tip_reserve_keys_reserve_serial_key UNIQUE (reserve_serial);
-
-
---
--- 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_serial);
-
-
---
--- Name: merchant_tip_reserves merchant_tip_reserves_reserve_pub_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserves
- ADD CONSTRAINT merchant_tip_reserves_reserve_pub_key UNIQUE (reserve_pub);
-
-
---
--- 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_serial);
-
-
---
--- Name: merchant_tips merchant_tips_tip_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tips
- ADD CONSTRAINT merchant_tips_tip_id_key UNIQUE (tip_id);
-
-
---
--- Name: merchant_transfer_signatures merchant_transfer_signatures_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfer_signatures
- ADD CONSTRAINT merchant_transfer_signatures_pkey PRIMARY KEY (credit_serial);
-
-
---
--- Name: merchant_transfer_to_coin merchant_transfer_to_coin_deposit_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfer_to_coin
- ADD CONSTRAINT merchant_transfer_to_coin_deposit_serial_key UNIQUE (deposit_serial);
-
-
---
--- Name: merchant_transfers merchant_transfers_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfers
- ADD CONSTRAINT merchant_transfers_pkey PRIMARY KEY (credit_serial);
-
-
---
--- Name: merchant_transfers merchant_transfers_wtid_exchange_url_account_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfers
- ADD CONSTRAINT merchant_transfers_wtid_exchange_url_account_serial_key UNIQUE (wtid, exchange_url, account_serial);
-
-
---
--- 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: recoup_refresh recoup_refresh_rrc_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup_refresh
- ADD CONSTRAINT recoup_refresh_rrc_serial_key UNIQUE (rrc_serial);
-
-
---
--- 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 (melt_serial_id, freshcoin_index);
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_rrc_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_rrc_serial_key UNIQUE (rrc_serial);
-
-
---
--- 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 (melt_serial_id);
-
-
---
--- Name: refresh_transfer_keys refresh_transfer_keys_rtc_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_transfer_keys
- ADD CONSTRAINT refresh_transfer_keys_rtc_serial_key UNIQUE (rtc_serial);
-
-
---
--- Name: refunds refunds_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refunds
- ADD CONSTRAINT refunds_pkey PRIMARY KEY (deposit_serial_id, 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_uuid, 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: reserves reserves_reserve_uuid_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves
- ADD CONSTRAINT reserves_reserve_uuid_key UNIQUE (reserve_uuid);
-
-
---
--- Name: revolving_work_shards revolving_work_shards_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.revolving_work_shards
- ADD CONSTRAINT revolving_work_shards_pkey PRIMARY KEY (job_name, start_row);
-
-
---
--- Name: revolving_work_shards revolving_work_shards_shard_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.revolving_work_shards
- ADD CONSTRAINT revolving_work_shards_shard_serial_id_key UNIQUE (shard_serial_id);
-
-
---
--- Name: signkey_revocations signkey_revocations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.signkey_revocations
- ADD CONSTRAINT signkey_revocations_pkey PRIMARY KEY (esk_serial);
-
-
---
--- Name: signkey_revocations signkey_revocations_signkey_revocations_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.signkey_revocations
- ADD CONSTRAINT signkey_revocations_signkey_revocations_serial_id_key UNIQUE (signkey_revocations_serial_id);
-
-
---
--- Name: wire_accounts wire_accounts_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_accounts
- ADD CONSTRAINT wire_accounts_pkey PRIMARY KEY (payto_uri);
-
-
---
--- 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_fee wire_fee_wire_fee_serial_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_fee
- ADD CONSTRAINT wire_fee_wire_fee_serial_key UNIQUE (wire_fee_serial);
-
-
---
--- 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: wire_targets wire_targets_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_targets
- ADD CONSTRAINT wire_targets_pkey PRIMARY KEY (h_payto);
-
-
---
--- Name: wire_targets wire_targets_wire_target_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_targets
- ADD CONSTRAINT wire_targets_wire_target_serial_id_key UNIQUE (wire_target_serial_id);
-
-
---
--- Name: work_shards work_shards_pkey; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.work_shards
- ADD CONSTRAINT work_shards_pkey PRIMARY KEY (job_name, start_row);
-
-
---
--- Name: work_shards work_shards_shard_serial_id_key; Type: CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.work_shards
- ADD CONSTRAINT work_shards_shard_serial_id_key UNIQUE (shard_serial_id);
-
-
---
--- 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 (known_coin_id, 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 (shard, done, extension_blocked, tiny, wire_deadline);
-
-
---
--- Name: deposits_iterate_matching_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX deposits_iterate_matching_index ON public.deposits USING btree (merchant_pub, wire_target_serial_id, done, extension_blocked, refund_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 (denominations_serial);
-
-
---
--- Name: known_coins_by_hashed_coin_pub; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX known_coins_by_hashed_coin_pub ON public.known_coins USING hash (coin_pub);
-
-
---
--- Name: known_coins_by_hashed_rc; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX known_coins_by_hashed_rc ON public.refresh_commitments USING hash (rc);
-
-
---
--- Name: merchant_contract_terms_by_merchant_and_expiration; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_contract_terms_by_merchant_and_expiration ON public.merchant_contract_terms USING btree (merchant_serial, pay_deadline);
-
-
---
--- Name: merchant_contract_terms_by_merchant_and_payment; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_contract_terms_by_merchant_and_payment ON public.merchant_contract_terms USING btree (merchant_serial, paid);
-
-
---
--- Name: merchant_contract_terms_by_merchant_session_and_fulfillment; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_contract_terms_by_merchant_session_and_fulfillment ON public.merchant_contract_terms USING btree (merchant_serial, fulfillment_url, session_id);
-
-
---
--- Name: merchant_inventory_locks_by_expiration; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_inventory_locks_by_expiration ON public.merchant_inventory_locks USING btree (expiration);
-
-
---
--- Name: merchant_inventory_locks_by_uuid; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_inventory_locks_by_uuid ON public.merchant_inventory_locks USING btree (lock_uuid);
-
-
---
--- Name: merchant_orders_by_creation_time; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_orders_by_creation_time ON public.merchant_orders USING btree (creation_time);
-
-
---
--- Name: merchant_orders_by_expiration; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_orders_by_expiration ON public.merchant_orders USING btree (pay_deadline);
-
-
---
--- Name: merchant_orders_locks_by_order_and_product; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_orders_locks_by_order_and_product ON public.merchant_order_locks USING btree (order_serial, product_serial);
-
-
---
--- Name: merchant_refunds_by_coin_and_order; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_refunds_by_coin_and_order ON public.merchant_refunds USING btree (coin_pub, order_serial);
-
-
---
--- Name: merchant_tip_reserves_by_exchange_balance; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_tip_reserves_by_exchange_balance ON public.merchant_tip_reserves USING btree (exchange_initial_balance_val, exchange_initial_balance_frac);
-
-
---
--- Name: merchant_tip_reserves_by_merchant_serial_and_creation_time; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_tip_reserves_by_merchant_serial_and_creation_time ON public.merchant_tip_reserves USING btree (merchant_serial, creation_time);
-
-
---
--- Name: merchant_tip_reserves_by_reserve_pub_and_merchant_serial; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_tip_reserves_by_reserve_pub_and_merchant_serial ON public.merchant_tip_reserves USING btree (reserve_pub, merchant_serial, creation_time);
-
-
---
--- Name: merchant_tips_by_pickup_and_expiration; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_tips_by_pickup_and_expiration ON public.merchant_tips USING btree (was_picked_up, expiration);
-
-
---
--- Name: merchant_transfers_by_credit; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX merchant_transfers_by_credit ON public.merchant_transfer_to_coin USING btree (credit_serial);
-
-
---
--- Name: prepare_get_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX prepare_get_index ON public.prewire USING btree (failed, finished);
-
-
---
--- Name: INDEX prepare_get_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.prepare_get_index IS 'for wire_prepare_data_get';
-
-
---
--- 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 gc_prewire';
-
-
---
--- Name: recoup_by_h_blind_ev; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_by_h_blind_ev ON public.recoup USING btree (reserve_out_serial_id);
-
-
---
--- Name: recoup_for_by_reserve; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_for_by_reserve ON public.recoup USING btree (known_coin_id, reserve_out_serial_id);
-
-
---
--- 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 (rrc_serial);
-
-
---
--- Name: recoup_refresh_for_by_reserve; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX recoup_refresh_for_by_reserve ON public.recoup_refresh USING btree (known_coin_id, rrc_serial);
-
-
---
--- Name: refresh_commitments_old_coin_id_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX refresh_commitments_old_coin_id_index ON public.refresh_commitments USING btree (old_known_coin_id);
-
-
---
--- Name: refresh_revealed_coins_denominations_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX refresh_revealed_coins_denominations_index ON public.refresh_revealed_coins USING btree (denominations_serial);
-
-
---
--- 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 (melt_serial_id, 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: reserves_close_by_uuid; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_close_by_uuid ON public.reserves_close USING btree (reserve_uuid);
-
-
---
--- 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 (denominations_serial, h_blind_ev);
-
-
---
--- Name: reserves_out_reserve_uuid_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX reserves_out_reserve_uuid_index ON public.reserves_out USING btree (reserve_uuid);
-
-
---
--- Name: INDEX reserves_out_reserve_uuid_index; Type: COMMENT; Schema: public; Owner: -
---
-
-COMMENT ON INDEX public.reserves_out_reserve_uuid_index IS 'for get_reserves_out';
-
-
---
--- Name: revolving_work_shards_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX revolving_work_shards_index ON public.revolving_work_shards USING btree (job_name, active, last_attempt);
-
-
---
--- 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: work_shards_index; Type: INDEX; Schema: public; Owner: -
---
-
-CREATE INDEX work_shards_index ON public.work_shards USING btree (job_name, completed, last_attempt);
-
-
---
--- 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_denom_sigs auditor_denom_sigs_auditor_uuid_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denom_sigs
- ADD CONSTRAINT auditor_denom_sigs_auditor_uuid_fkey FOREIGN KEY (auditor_uuid) REFERENCES public.auditors(auditor_uuid) ON DELETE CASCADE;
-
-
---
--- Name: auditor_denom_sigs auditor_denom_sigs_denominations_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.auditor_denom_sigs
- ADD CONSTRAINT auditor_denom_sigs_denominations_serial_fkey FOREIGN KEY (denominations_serial) REFERENCES public.denominations(denominations_serial) 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_denominations_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.denomination_revocations
- ADD CONSTRAINT denomination_revocations_denominations_serial_fkey FOREIGN KEY (denominations_serial) REFERENCES public.denominations(denominations_serial) ON DELETE CASCADE;
-
-
---
--- Name: deposits deposits_extension_details_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits
- ADD CONSTRAINT deposits_extension_details_serial_id_fkey FOREIGN KEY (extension_details_serial_id) REFERENCES public.extension_details(extension_details_serial_id);
-
-
---
--- Name: deposits deposits_known_coin_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits
- ADD CONSTRAINT deposits_known_coin_id_fkey FOREIGN KEY (known_coin_id) REFERENCES public.known_coins(known_coin_id) ON DELETE CASCADE;
-
-
---
--- Name: deposits deposits_wire_target_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.deposits
- ADD CONSTRAINT deposits_wire_target_serial_id_fkey FOREIGN KEY (wire_target_serial_id) REFERENCES public.wire_targets(wire_target_serial_id);
-
-
---
--- Name: known_coins known_coins_denominations_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.known_coins
- ADD CONSTRAINT known_coins_denominations_serial_fkey FOREIGN KEY (denominations_serial) REFERENCES public.denominations(denominations_serial) 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_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_accounts merchant_accounts_merchant_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_accounts
- ADD CONSTRAINT merchant_accounts_merchant_serial_fkey FOREIGN KEY (merchant_serial) REFERENCES public.merchant_instances(merchant_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_contract_terms merchant_contract_terms_merchant_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_contract_terms
- ADD CONSTRAINT merchant_contract_terms_merchant_serial_fkey FOREIGN KEY (merchant_serial) REFERENCES public.merchant_instances(merchant_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_deposit_to_transfer merchant_deposit_to_transfer_credit_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposit_to_transfer
- ADD CONSTRAINT merchant_deposit_to_transfer_credit_serial_fkey FOREIGN KEY (credit_serial) REFERENCES public.merchant_transfers(credit_serial);
-
-
---
--- Name: merchant_deposit_to_transfer merchant_deposit_to_transfer_deposit_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposit_to_transfer
- ADD CONSTRAINT merchant_deposit_to_transfer_deposit_serial_fkey FOREIGN KEY (deposit_serial) REFERENCES public.merchant_deposits(deposit_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_deposit_to_transfer merchant_deposit_to_transfer_signkey_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposit_to_transfer
- ADD CONSTRAINT merchant_deposit_to_transfer_signkey_serial_fkey FOREIGN KEY (signkey_serial) REFERENCES public.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_deposits merchant_deposits_account_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits
- ADD CONSTRAINT merchant_deposits_account_serial_fkey FOREIGN KEY (account_serial) REFERENCES public.merchant_accounts(account_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_deposits merchant_deposits_order_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits
- ADD CONSTRAINT merchant_deposits_order_serial_fkey FOREIGN KEY (order_serial) REFERENCES public.merchant_contract_terms(order_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_deposits merchant_deposits_signkey_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_deposits
- ADD CONSTRAINT merchant_deposits_signkey_serial_fkey FOREIGN KEY (signkey_serial) REFERENCES public.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_inventory_locks merchant_inventory_locks_product_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_inventory_locks
- ADD CONSTRAINT merchant_inventory_locks_product_serial_fkey FOREIGN KEY (product_serial) REFERENCES public.merchant_inventory(product_serial);
-
-
---
--- Name: merchant_inventory merchant_inventory_merchant_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_inventory
- ADD CONSTRAINT merchant_inventory_merchant_serial_fkey FOREIGN KEY (merchant_serial) REFERENCES public.merchant_instances(merchant_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_keys merchant_keys_merchant_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_keys
- ADD CONSTRAINT merchant_keys_merchant_serial_fkey FOREIGN KEY (merchant_serial) REFERENCES public.merchant_instances(merchant_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_kyc merchant_kyc_account_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_kyc
- ADD CONSTRAINT merchant_kyc_account_serial_fkey FOREIGN KEY (account_serial) REFERENCES public.merchant_accounts(account_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_order_locks merchant_order_locks_order_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_order_locks
- ADD CONSTRAINT merchant_order_locks_order_serial_fkey FOREIGN KEY (order_serial) REFERENCES public.merchant_orders(order_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_order_locks merchant_order_locks_product_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_order_locks
- ADD CONSTRAINT merchant_order_locks_product_serial_fkey FOREIGN KEY (product_serial) REFERENCES public.merchant_inventory(product_serial);
-
-
---
--- Name: merchant_orders merchant_orders_merchant_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_orders
- ADD CONSTRAINT merchant_orders_merchant_serial_fkey FOREIGN KEY (merchant_serial) REFERENCES public.merchant_instances(merchant_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_refund_proofs merchant_refund_proofs_refund_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refund_proofs
- ADD CONSTRAINT merchant_refund_proofs_refund_serial_fkey FOREIGN KEY (refund_serial) REFERENCES public.merchant_refunds(refund_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_refund_proofs merchant_refund_proofs_signkey_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refund_proofs
- ADD CONSTRAINT merchant_refund_proofs_signkey_serial_fkey FOREIGN KEY (signkey_serial) REFERENCES public.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_refunds merchant_refunds_order_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_refunds
- ADD CONSTRAINT merchant_refunds_order_serial_fkey FOREIGN KEY (order_serial) REFERENCES public.merchant_contract_terms(order_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_tip_pickup_signatures merchant_tip_pickup_signatures_pickup_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_pickup_signatures
- ADD CONSTRAINT merchant_tip_pickup_signatures_pickup_serial_fkey FOREIGN KEY (pickup_serial) REFERENCES public.merchant_tip_pickups(pickup_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_tip_pickups merchant_tip_pickups_tip_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_pickups
- ADD CONSTRAINT merchant_tip_pickups_tip_serial_fkey FOREIGN KEY (tip_serial) REFERENCES public.merchant_tips(tip_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_tip_reserve_keys merchant_tip_reserve_keys_reserve_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserve_keys
- ADD CONSTRAINT merchant_tip_reserve_keys_reserve_serial_fkey FOREIGN KEY (reserve_serial) REFERENCES public.merchant_tip_reserves(reserve_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_tip_reserves merchant_tip_reserves_merchant_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tip_reserves
- ADD CONSTRAINT merchant_tip_reserves_merchant_serial_fkey FOREIGN KEY (merchant_serial) REFERENCES public.merchant_instances(merchant_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_tips merchant_tips_reserve_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_tips
- ADD CONSTRAINT merchant_tips_reserve_serial_fkey FOREIGN KEY (reserve_serial) REFERENCES public.merchant_tip_reserves(reserve_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_transfer_signatures merchant_transfer_signatures_credit_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfer_signatures
- ADD CONSTRAINT merchant_transfer_signatures_credit_serial_fkey FOREIGN KEY (credit_serial) REFERENCES public.merchant_transfers(credit_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_transfer_signatures merchant_transfer_signatures_signkey_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfer_signatures
- ADD CONSTRAINT merchant_transfer_signatures_signkey_serial_fkey FOREIGN KEY (signkey_serial) REFERENCES public.merchant_exchange_signing_keys(signkey_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_transfer_to_coin merchant_transfer_to_coin_credit_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfer_to_coin
- ADD CONSTRAINT merchant_transfer_to_coin_credit_serial_fkey FOREIGN KEY (credit_serial) REFERENCES public.merchant_transfers(credit_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_transfer_to_coin merchant_transfer_to_coin_deposit_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfer_to_coin
- ADD CONSTRAINT merchant_transfer_to_coin_deposit_serial_fkey FOREIGN KEY (deposit_serial) REFERENCES public.merchant_deposits(deposit_serial) ON DELETE CASCADE;
-
-
---
--- Name: merchant_transfers merchant_transfers_account_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.merchant_transfers
- ADD CONSTRAINT merchant_transfers_account_serial_fkey FOREIGN KEY (account_serial) REFERENCES public.merchant_accounts(account_serial) ON DELETE CASCADE;
-
-
---
--- Name: recoup recoup_known_coin_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup
- ADD CONSTRAINT recoup_known_coin_id_fkey FOREIGN KEY (known_coin_id) REFERENCES public.known_coins(known_coin_id);
-
-
---
--- Name: recoup_refresh recoup_refresh_known_coin_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup_refresh
- ADD CONSTRAINT recoup_refresh_known_coin_id_fkey FOREIGN KEY (known_coin_id) REFERENCES public.known_coins(known_coin_id);
-
-
---
--- Name: recoup_refresh recoup_refresh_rrc_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup_refresh
- ADD CONSTRAINT recoup_refresh_rrc_serial_fkey FOREIGN KEY (rrc_serial) REFERENCES public.refresh_revealed_coins(rrc_serial) ON DELETE CASCADE;
-
-
---
--- Name: recoup recoup_reserve_out_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.recoup
- ADD CONSTRAINT recoup_reserve_out_serial_id_fkey FOREIGN KEY (reserve_out_serial_id) REFERENCES public.reserves_out(reserve_out_serial_id) ON DELETE CASCADE;
-
-
---
--- Name: refresh_commitments refresh_commitments_old_known_coin_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_commitments
- ADD CONSTRAINT refresh_commitments_old_known_coin_id_fkey FOREIGN KEY (old_known_coin_id) REFERENCES public.known_coins(known_coin_id) ON DELETE CASCADE;
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_denominations_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_denominations_serial_fkey FOREIGN KEY (denominations_serial) REFERENCES public.denominations(denominations_serial) ON DELETE CASCADE;
-
-
---
--- Name: refresh_revealed_coins refresh_revealed_coins_melt_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_revealed_coins
- ADD CONSTRAINT refresh_revealed_coins_melt_serial_id_fkey FOREIGN KEY (melt_serial_id) REFERENCES public.refresh_commitments(melt_serial_id) ON DELETE CASCADE;
-
-
---
--- Name: refresh_transfer_keys refresh_transfer_keys_melt_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refresh_transfer_keys
- ADD CONSTRAINT refresh_transfer_keys_melt_serial_id_fkey FOREIGN KEY (melt_serial_id) REFERENCES public.refresh_commitments(melt_serial_id) ON DELETE CASCADE;
-
-
---
--- Name: refunds refunds_deposit_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.refunds
- ADD CONSTRAINT refunds_deposit_serial_id_fkey FOREIGN KEY (deposit_serial_id) REFERENCES public.deposits(deposit_serial_id) ON DELETE CASCADE;
-
-
---
--- Name: reserves_close reserves_close_reserve_uuid_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_close
- ADD CONSTRAINT reserves_close_reserve_uuid_fkey FOREIGN KEY (reserve_uuid) REFERENCES public.reserves(reserve_uuid) ON DELETE CASCADE;
-
-
---
--- Name: reserves_close reserves_close_wire_target_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_close
- ADD CONSTRAINT reserves_close_wire_target_serial_id_fkey FOREIGN KEY (wire_target_serial_id) REFERENCES public.wire_targets(wire_target_serial_id);
-
-
---
--- Name: reserves_in reserves_in_reserve_uuid_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_in
- ADD CONSTRAINT reserves_in_reserve_uuid_fkey FOREIGN KEY (reserve_uuid) REFERENCES public.reserves(reserve_uuid) ON DELETE CASCADE;
-
-
---
--- Name: reserves_in reserves_in_wire_source_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_in
- ADD CONSTRAINT reserves_in_wire_source_serial_id_fkey FOREIGN KEY (wire_source_serial_id) REFERENCES public.wire_targets(wire_target_serial_id);
-
-
---
--- Name: reserves_out reserves_out_denominations_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_out
- ADD CONSTRAINT reserves_out_denominations_serial_fkey FOREIGN KEY (denominations_serial) REFERENCES public.denominations(denominations_serial);
-
-
---
--- Name: reserves_out reserves_out_reserve_uuid_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.reserves_out
- ADD CONSTRAINT reserves_out_reserve_uuid_fkey FOREIGN KEY (reserve_uuid) REFERENCES public.reserves(reserve_uuid) ON DELETE CASCADE;
-
-
---
--- Name: signkey_revocations signkey_revocations_esk_serial_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.signkey_revocations
- ADD CONSTRAINT signkey_revocations_esk_serial_fkey FOREIGN KEY (esk_serial) REFERENCES public.exchange_sign_keys(esk_serial) 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;
-
-
---
--- Name: wire_out wire_out_wire_target_serial_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
---
-
-ALTER TABLE ONLY public.wire_out
- ADD CONSTRAINT wire_out_wire_target_serial_id_fkey FOREIGN KEY (wire_target_serial_id) REFERENCES public.wire_targets(wire_target_serial_id);
-
-
---
--- 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 54f8152a5..4cb46f470 100644
--- a/src/auditor/taler-auditor-dbinit.c
+++ b/src/auditor/taler-auditor-dbinit.c
@@ -90,7 +90,9 @@ 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");
diff --git a/src/auditor/taler-auditor-exchange.c b/src/auditor/taler-auditor-exchange.c
deleted file mode 100644
index 04181ce3f..000000000
--- a/src/auditor/taler-auditor-exchange.c
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file 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));
- {
- int ret;
-
- ret = GNUNET_GETOPT_run ("taler-auditor-exchange",
- options,
- argc, argv);
- if (GNUNET_NO == ret)
- return EXIT_SUCCESS;
- if (GNUNET_SYSERR == ret)
- return EXIT_INVALIDARGUMENT;
- }
- if (NULL == cfgfile)
- cfgfile = GNUNET_CONFIGURATION_default_filename ();
- if (NULL == cfgfile)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Can't find default configuration file.\n");
- return EXIT_NOTCONFIGURED;
- }
- 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 (cfgfile);
- return EXIT_NOTCONFIGURED;
- }
- GNUNET_free (cfgfile);
-
- if (! remove_flag)
- {
- if (NULL == exchange_url)
- {
- fprintf (stderr,
- _ ("Missing either `%s' or `%s'.\n"),
- "-u URL",
- "--remove");
- return EXIT_INVALIDARGUMENT;
- }
- 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 EXIT_INVALIDARGUMENT;
- }
- }
-
-
- if (NULL ==
- (adb = TALER_AUDITORDB_plugin_load (cfg)))
- {
- fprintf (stderr,
- "Failed to initialize auditor database plugin.\n");
- return EXIT_NOTINSTALLED;
- }
-
- /* 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 EXIT_NOPERMISSION;
- }
-
- /* Update DB */
- {
- enum GNUNET_DB_QueryStatus qs;
-
- if (GNUNET_SYSERR ==
- adb->preflight (adb->cls))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to initialize database connection\n");
- TALER_AUDITORDB_plugin_unload (adb);
- return EXIT_FAILURE;
- }
-
- if (remove_flag)
- {
- qs = adb->delete_exchange (adb->cls,
- &master_public_key);
- }
- else
- {
- qs = adb->insert_exchange (adb->cls,
- &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 EXIT_FAILURE;
- }
- 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 EXIT_FAILURE;
- }
- }
- TALER_AUDITORDB_plugin_unload (adb);
- return EXIT_SUCCESS;
-}
-
-
-/* end of taler-auditor-exchange.c */
diff --git a/src/auditor/taler-auditor-httpd.c b/src/auditor/taler-auditor-httpd.c
index a212eddca..59bd849bc 100644
--- a/src/auditor/taler-auditor-httpd.c
+++ b/src/auditor/taler-auditor-httpd.c
@@ -31,7 +31,7 @@
#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"
@@ -48,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.
@@ -81,6 +82,12 @@ struct TALER_EXCHANGEDB_Plugin *TAH_eplugin;
static struct TALER_AuditorPublicKeyP auditor_pub;
/**
+ * Exchange master public key (according to the
+ * configuration). (global)
+ */
+struct TALER_MasterPublicKeyP TAH_master_public_key;
+
+/**
* Default timeout in seconds for HTTP requests.
*/
static unsigned int connection_timeout = 30;
@@ -132,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
@@ -142,11 +149,11 @@ handle_mhd_completion_callback (void *cls,
* @return MHD result code
*/
static MHD_RESULT
-handle_version (struct TAH_RequestHandler *rh,
- struct MHD_Connection *connection,
- void **connection_cls,
- const char *upload_data,
- size_t *upload_data_size)
+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! */
@@ -157,12 +164,18 @@ handle_version (struct TAH_RequestHandler *rh,
if (NULL == ver)
{
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));
+ &auditor_pub),
+ GNUNET_JSON_pack_data_auto ("exchange_master_public_key",
+ &TAH_master_public_key));
}
if (NULL == ver)
{
@@ -204,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",
@@ -298,6 +314,40 @@ auditor_serve_process_config (void)
{
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;
diff --git a/src/auditor/taler-auditor-httpd.h b/src/auditor/taler-auditor-httpd.h
index 79e56ff55..853722f09 100644
--- a/src/auditor/taler-auditor-httpd.h
+++ b/src/auditor/taler-auditor-httpd.h
@@ -39,6 +39,12 @@ extern struct TALER_AUDITORDB_Plugin *TAH_plugin;
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;
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 694753c8d..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,6 +31,49 @@
#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
@@ -65,17 +108,20 @@ verify_and_execute_deposit_confirmation (
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_HashCode h;
const char *cached;
- struct TALER_ExchangeSigningKeyValidityPS skv = {
- .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY),
- .purpose.size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS)),
+ 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)];
+
+ 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) )
+ GNUNET_TIME_absolute_is_past (es->ep_expire.abs_time))
{
/* Signing key expired */
TALER_LOG_WARNING ("Expired exchange signing key\n");
@@ -87,7 +133,7 @@ verify_and_execute_deposit_confirmation (
/* check our cache */
GNUNET_CRYPTO_hash (&skv,
- sizeof (skv),
+ sizeof(skv),
&h);
GNUNET_assert (0 == pthread_mutex_lock (&lock));
cached = GNUNET_CONTAINER_multihashmap_get (cache,
@@ -111,7 +157,7 @@ verify_and_execute_deposit_confirmation (
es->ep_start,
es->ep_expire,
es->ep_end,
- &es->master_public_key,
+ &TAH_master_public_key,
&es->master_sig))
{
TALER_LOG_WARNING ("Invalid signature on exchange signing key\n");
@@ -182,17 +228,19 @@ verify_and_execute_deposit_confirmation (
/* check deposit confirmation signature */
if (GNUNET_OK !=
- TALER_exchange_deposit_confirm_verify (&dc->h_contract_terms,
- &dc->h_wire,
- NULL /* h_extensions! */,
- dc->exchange_timestamp,
- dc->wire_deadline,
- dc->refund_deadline,
- &dc->amount_without_fee,
- &dc->coin_pub,
- &dc->merchant,
- &dc->exchange_pub,
- &dc->exchange_sig))
+ 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");
@@ -221,55 +269,48 @@ verify_and_execute_deposit_confirmation (
}
-/**
- * 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
- */
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)
+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_extensions",
- &dc.h_extensions),
+ 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_timestamp ("refund_deadline",
- &dc.refund_deadline),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("refund_deadline",
+ &dc.refund_deadline),
+ NULL),
GNUNET_JSON_spec_timestamp ("wire_deadline",
&dc.wire_deadline),
- TALER_JSON_spec_amount ("amount_without_fee",
+ TALER_JSON_spec_amount ("total_without_fee",
TAH_currency,
- &dc.amount_without_fee),
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &dc.coin_pub),
+ &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_fixed_auto ("master_pub",
- &es.master_public_key),
GNUNET_JSON_spec_timestamp ("ep_start",
&es.ep_start),
GNUNET_JSON_spec_timestamp ("ep_expire",
@@ -280,13 +321,14 @@ TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh,
&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;
enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_post_json (connection,
@@ -296,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)
{
+ 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)
{
@@ -335,9 +440,6 @@ TEAH_DEPOSIT_CONFIRMATION_init (void)
}
-/**
- * Shut down subsystem.
- */
void
TEAH_DEPOSIT_CONFIRMATION_done (void)
{
@@ -348,6 +450,3 @@ TEAH_DEPOSIT_CONFIRMATION_done (void)
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 a7c331916..1226dda69 100644
--- a/src/auditor/taler-auditor-httpd_deposit-confirmation.h
+++ b/src/auditor/taler-auditor-httpd_deposit-confirmation.h
@@ -56,4 +56,5 @@ TAH_DEPOSIT_CONFIRMATION_handler (struct TAH_RequestHandler *rh,
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 f9a9e9e60..000000000
--- a/src/auditor/taler-auditor-httpd_exchanges.c
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU 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 = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("master_pub",
- master_pub),
- GNUNET_JSON_pack_string ("exchange_url",
- exchange_url));
- 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
- */
-MHD_RESULT
-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;
- 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);
- qs = TAH_plugin->list_exchanges (TAH_plugin->cls,
- &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_GENERIC_DB_FETCH_FAILED,
- "exchanges");
- }
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("exchanges",
- ja));
-}
-
-
-/* end of taler-auditor-httpd_exchanges.c */
diff --git a/src/auditor/taler-auditor-sync.c b/src/auditor/taler-auditor-sync.c
index 17c4da467..e4022d325 100644
--- a/src/auditor/taler-auditor-sync.c
+++ b/src/auditor/taler-auditor-sync.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ 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
@@ -91,9 +91,14 @@ struct Table
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},
@@ -103,13 +108,30 @@ static struct Table tables[] = {
{ .rt = TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS},
{ .rt = TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS},
{ .rt = TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS},
- { .rt = TALER_EXCHANGEDB_RT_DEPOSITS},
+ { .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 }
};
@@ -139,7 +161,7 @@ struct InsertContext
* @return #GNUNET_OK to continue to iterate,
* #GNUNET_SYSERR to fail with an error
*/
-static int
+static enum GNUNET_GenericReturnValue
do_insert (void *cls,
const struct TALER_EXCHANGEDB_TableData *td)
{
@@ -202,20 +224,16 @@ transact (void)
src->lookup_serial_by_table (src->cls,
tables[i].rt,
&tables[i].end_serial);
- if (0 >
- src->commit (src->cls))
- return GNUNET_SYSERR;
+ src->rollback (src->cls);
if (GNUNET_OK !=
- dst->start (src->cls,
+ 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);
- if (0 >
- dst->commit (dst->cls))
- return GNUNET_SYSERR;
+ dst->rollback (dst->cls);
for (unsigned int i = 0; ! tables[i].end; i++)
{
struct Table *table = &tables[i];
@@ -376,7 +394,7 @@ do_sync (void *cls)
* @param value actual value of the option (a string)
* @return #GNUNET_OK
*/
-static int
+static enum GNUNET_GenericReturnValue
set_filename (struct GNUNET_GETOPT_CommandLineProcessorContext *ctx,
void *scls,
const char *option,
@@ -588,6 +606,9 @@ main (int argc,
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))
{
diff --git a/src/auditor/taler-auditor.in b/src/auditor/taler-auditor.in
index c8ea6b0c9..ab3d8d202 100644
--- a/src/auditor/taler-auditor.in
+++ b/src/auditor/taler-auditor.in
@@ -11,6 +11,7 @@ Arguments mandatory for long options are also mandatory for short options.
-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
@@ -28,7 +29,7 @@ EOF
function optcheck {
-TEMP=`getopt -o c:hiL:l:m:T:v --long config:,help,internal,log:,logfile:exchange-key:,timetravel:,version -n 'taler-auditor' -- "$@"`
+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
@@ -43,6 +44,7 @@ DEBUG=false
MEMORY=
DEBUGFILE=
JAVA_MISC_OPT=
+INF=
while true; do
case "$1" in
-c | --config ) shift 2 ;;
@@ -51,6 +53,7 @@ while true; do
exit 0
;;
-i | --internal ) shift ;;
+ -I | --ignore-not-found ) INF="-I"; shift ;;
-L | --log ) shift 2;;
-l | --logfile ) shift ;;
-m | --exchange-key ) shift 2 ;;
@@ -74,16 +77,23 @@ done
}
# End of function 'optcheck'
-
optcheck "$@"
+# Remove "-I" from $@ if present, store result in $ARGS.
+ARGS=("$@")
+ARGS=(${ARGS[@]/$INF})
-DIR=`mktemp -d reportXXXXXX`
-for n in aggregation coins deposits reserves wire
+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 \
diff --git a/src/auditor/taler-helper-auditor-aggregation.c b/src/auditor/taler-helper-auditor-aggregation.c
index 7fffcd284..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-2021 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;
@@ -305,14 +309,9 @@ struct WireFeeInfo
struct GNUNET_TIME_Timestamp end_date;
/**
- * How high is the wire fee.
- */
- struct TALER_Amount wire_fee;
-
- /**
- * How high is the closing fee.
+ * How high are the wire fees.
*/
- struct TALER_Amount closing_fee;
+ struct TALER_WireFeeSet fees;
};
@@ -390,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 TALER_PrivateContractHash *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)
@@ -403,9 +402,9 @@ 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",
@@ -426,25 +425,34 @@ check_transaction_history_for_deposit (
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 */
- amount_with_fee = &tl->details.deposit->amount_with_fee; /* according to exchange*/
+ if (NULL != deposited)
+ {
+ 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")));
+ }
+ deposited = &tl->details.deposit->amount_with_fee; /* according to exchange*/
fee_claimed = &tl->details.deposit->deposit_fee; /* Fee according to exchange DB */
TALER_ARL_amount_add (&expenditures,
&expenditures,
- amount_with_fee);
+ 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,
@@ -455,7 +463,7 @@ check_transaction_history_for_deposit (
struct TALER_Amount amount_without_fee;
TALER_ARL_amount_subtract (&amount_without_fee,
- amount_with_fee,
+ deposited,
fee_claimed);
TALER_ARL_amount_add (merchant_gain,
merchant_gain,
@@ -466,120 +474,168 @@ check_transaction_history_for_deposit (
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;
+ /* 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:
+ {
+ const struct TALER_Amount *amount_with_fee;
- /* Fee according to denomination data of auditor */
- TALER_amount_ntoh (&fee_expected,
- &issue->fee_deposit);
+ 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 auditor and exchange DB! */
- report_amount_arithmetic_inconsistency ("deposit fee",
+ /* Disagreement in fee structure between exchange and auditor */
+ report_amount_arithmetic_inconsistency ("melt fee",
0,
fee_claimed,
- &fee_expected,
+ &issue->fees.refresh,
1);
}
+ break;
}
- break;
- case TALER_EXCHANGEDB_TT_MELT:
- 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 */
+ case TALER_EXCHANGEDB_TT_REFUND:
{
- struct TALER_Amount fee_expected;
+ const struct TALER_Amount *amount_with_fee;
- TALER_amount_ntoh (&fee_expected,
- &issue->fee_refresh);
+ 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_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);
+ }
+ /* 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 ("melt fee",
+ /* Disagreement in fee structure between exchange and auditor! */
+ report_amount_arithmetic_inconsistency ("refund fee",
0,
fee_claimed,
- &fee_expected,
+ &issue->fees.refund,
1);
}
+ break;
}
- break;
- case TALER_EXCHANGEDB_TT_REFUND:
- 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)) )
+ case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
{
- 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,
+ 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);
- /* If there is a refund, we give back the deposit fee */
- refund_deposit_fee = GNUNET_YES;
+ break;
}
- /* Check that the fees given in the transaction list and in dki match */
+ case TALER_EXCHANGEDB_TT_RECOUP:
{
- struct TALER_Amount fee_expected;
+ const struct TALER_Amount *amount_with_fee;
- TALER_amount_ntoh (&fee_expected,
- &issue->fee_refund);
+ /* 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;
+ }
+ case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
+ {
+ 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;
+ }
+ 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 (&fee_expected,
+ 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,
- &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 */
- TALER_ARL_amount_add (&refunds,
- &refunds,
- amount_with_fee);
- 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;
- TALER_ARL_amount_add (&expenditures,
- &expenditures,
- amount_with_fee);
- 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;
- TALER_ARL_amount_add (&expenditures,
- &expenditures,
- amount_with_fee);
- 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,
@@ -589,11 +645,15 @@ 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 */
+ /* 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);
@@ -646,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);
}
}
@@ -683,6 +739,7 @@ check_transaction_history_for_deposit (
* @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 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
@@ -698,26 +755,52 @@ wire_transfer_information_cb (
uint64_t rowid,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const char *account_pay_uri,
+ const struct TALER_PaytoHashP *h_payto,
struct GNUNET_TIME_Timestamp exec_time,
- const struct TALER_PrivateContractHash *h_contract_terms,
+ 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 TALER_PaytoHashP hpt;
+ uint64_t etag_out;
+
+ TALER_payto_hash (account_pay_uri,
+ &hpt);
+ if (0 !=
+ GNUNET_memcmp (&hpt,
+ h_payto))
+ {
+ report_row_inconsistency ("wire_targets",
+ rowid,
+ "h-payto does not match payto URI");
+ }
/* Obtain coin's transaction history */
- qs = TALER_ARL_edb->get_coin_transactions (TALER_ARL_edb->cls,
- coin_pub,
- GNUNET_YES,
- &tl);
+ /* TODO: could use 'start' 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,
+ coin_pub,
+ 0,
+ 0,
+ &etag_out,
+ &balance,
+ &h_denom_pub,
+ &tl);
+ }
if ( (qs < 0) ||
(NULL == tl) )
{
@@ -898,7 +981,7 @@ get_wire_fee (struct AggregationContext *ac,
GNUNET_TIME_timestamp_cmp (pos->end_date,
>,
timestamp) )
- return &pos->wire_fee;
+ return &pos->fees.wire;
if (GNUNET_TIME_timestamp_cmp (pos->start_date,
>,
timestamp))
@@ -913,8 +996,7 @@ get_wire_fee (struct AggregationContext *ac,
timestamp,
&wfi->start_date,
&wfi->end_date,
- &wfi->wire_fee,
- &wfi->closing_fee,
+ &wfi->fees,
&master_sig))
{
GNUNET_break (0);
@@ -931,8 +1013,7 @@ get_wire_fee (struct AggregationContext *ac,
method,
wfi->start_date,
wfi->end_date,
- &wfi->wire_fee,
- &wfi->closing_fee,
+ &wfi->fees,
&TALER_ARL_master_pub,
&master_sig))
{
@@ -945,7 +1026,7 @@ 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),
+ TALER_amount2s (&wfi->fees.wire),
GNUNET_TIME_timestamp2s (wfi->start_date));
if ( (NULL == pos) ||
(NULL == pos->prev) )
@@ -986,7 +1067,7 @@ get_wire_fee (struct AggregationContext *ac,
TALER_JSON_pack_time_abs_human ("time",
wfi->end_date.abs_time)));
}
- return &wfi->wire_fee;
+ return &wfi->fees.wire;
}
@@ -1018,8 +1099,9 @@ 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",
@@ -1106,8 +1188,8 @@ check_wire_out_cb (void *cls,
&wcc.total_deposits,
&final_amount);
/* Sum up aggregation fees (we simply include the rounding gains) */
- TALER_ARL_amount_add (&total_aggregation_fee_income,
- &total_aggregation_fee_income,
+ 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 */
@@ -1179,9 +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_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);
@@ -1194,18 +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_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);
@@ -1214,7 +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,
- ppa.last_wire_out_serial_id,
+ TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id),
&check_wire_out_cb,
&ac);
if (0 > qs)
@@ -1240,30 +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_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_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_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_master_pub,
- &ppa);
+ TALER_ARL_SET_PP (aggregation_last_wire_out_serial_id),
+ NULL);
if (0 >= qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -1273,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;
}
@@ -1308,7 +1393,8 @@ run (void *cls,
"Starting audit\n");
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &total_aggregation_fee_income));
+ &TALER_ARL_USE_AB (
+ aggregation_total_wire_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
&total_wire_out_delta_plus));
@@ -1402,14 +1488,14 @@ run (void *cls,
"total_arithmetic_delta_minus",
&total_arithmetic_delta_minus),
TALER_JSON_pack_amount (
- "total_aggregation_fee_income",
- &total_aggregation_fee_income),
+ "aggregation_total_wire_fee_revenue",
+ &TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue)),
GNUNET_JSON_pack_uint64 (
"start_ppa_wire_out_serial_id",
- ppa_start.last_wire_out_serial_id),
+ 0 /* defunct */),
GNUNET_JSON_pack_uint64 (
"end_ppa_wire_out_serial_id",
- ppa.last_wire_out_serial_id),
+ TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id)),
/* block #4 */
TALER_JSON_pack_time_abs_human (
"auditor_start_time",
@@ -1439,11 +1525,10 @@ main (int argc,
"internal",
"perform checks only applicable for exchange-internal audits",
&internal_checks),
- 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 ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
GNUNET_GETOPT_option_timetravel ('T',
"timetravel"),
GNUNET_GETOPT_OPTION_END
diff --git a/src/auditor/taler-helper-auditor-coins.c b/src/auditor/taler-helper-auditor-coins.c
index 3473a8284..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-2021 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,7 +90,7 @@ 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;
@@ -115,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.
@@ -156,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.
@@ -210,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;
}
@@ -248,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",
@@ -276,7 +258,7 @@ 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)
{
@@ -293,12 +275,12 @@ report_emergency_by_amount (
risk),
TALER_JSON_pack_amount ("denom_loss",
loss),
- TALER_JSON_pack_time_abs_nbo_human ("start",
- issue->start.abs_time_nbo),
- TALER_JSON_pack_time_abs_nbo_human ("deposit_end",
- issue->expire_deposit.abs_time_nbo),
- TALER_JSON_pack_amount_nbo ("value",
- &issue->value)));
+ 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);
@@ -324,13 +306,11 @@ 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,
GNUNET_JSON_PACK (
@@ -342,21 +322,19 @@ report_emergency_by_count (
num_known),
TALER_JSON_pack_amount ("denom_risk",
risk),
- TALER_JSON_pack_time_abs_nbo_human ("start",
- issue->start.abs_time_nbo),
- TALER_JSON_pack_time_abs_nbo_human ("deposit_end",
- issue->expire_deposit.abs_time_nbo),
- TALER_JSON_pack_amount_nbo ("value",
- &issue->value)));
+ 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);
- TALER_amount_ntoh (&denom_value,
- &issue->value);
for (uint64_t i = num_issued; i<num_known; i++)
TALER_ARL_amount_add (&reported_emergency_loss_by_count,
&reported_emergency_loss_by_count,
- &denom_value);
+ &issue->value);
}
@@ -475,15 +453,27 @@ 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,
- 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_set_zero (value->currency,
&refunded));
@@ -493,7 +483,7 @@ check_coin_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
GNUNET_assert (GNUNET_OK ==
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)
@@ -521,7 +511,7 @@ check_coin_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
TALER_ARL_amount_add (&spent,
&spent,
&pos->details.refund->refund_fee);
- have_refund = GNUNET_YES;
+ have_refund = true;
break;
case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
/* refunded += pos->value */
@@ -541,8 +531,28 @@ check_coin_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
&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)
{
@@ -593,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;
};
@@ -673,7 +659,7 @@ struct CoinContext
* @return transaction status code
*/
static enum GNUNET_DB_QueryStatus
-init_denomination (const struct TALER_DenominationHash *denom_hash,
+init_denomination (const struct TALER_DenominationHashP *denom_hash,
struct DenominationSummary *ds)
{
enum GNUNET_DB_QueryStatus qs;
@@ -682,11 +668,7 @@ init_denomination (const struct TALER_DenominationHash *denom_hash,
qs = TALER_ARL_adb->get_denomination_balance (TALER_ARL_adb->cls,
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);
@@ -694,28 +676,28 @@ init_denomination (const struct TALER_DenominationHash *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_set_zero (TALER_ARL_currency,
- &ds->denom_balance));
+ &ds->dcd.denom_balance));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &ds->denom_loss));
+ &ds->dcd.denom_loss));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &ds->denom_risk));
+ &ds->dcd.denom_risk));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &ds->denom_recoup));
+ &ds->dcd.recoup_loss));
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Starting balance for denomination `%s' is %s (%llu)\n",
GNUNET_h2s (&denom_hash->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);
qs = TALER_ARL_edb->get_denomination_revocation (TALER_ARL_edb->cls,
denom_hash,
&msig,
@@ -740,10 +722,10 @@ init_denomination (const struct TALER_DenominationHash *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;
}
@@ -758,9 +740,10 @@ init_denomination (const struct TALER_DenominationHash *denom_hash,
* @return NULL on error
*/
static struct DenominationSummary *
-get_denomination_summary (struct CoinContext *cc,
- const struct TALER_DenominationKeyValidityPS *issue,
- const struct TALER_DenominationHash *dh)
+get_denomination_summary (
+ struct CoinContext *cc,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue,
+ const struct TALER_DenominationHashP *dh)
{
struct DenominationSummary *ds;
@@ -802,18 +785,18 @@ sync_denomination (void *cls,
void *value)
{
struct CoinContext *cc = cls;
- struct TALER_DenominationHash denom_h = {
+ 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_Timestamp expire_deposit;
struct GNUNET_TIME_Absolute expire_deposit_grace;
enum GNUNET_DB_QueryStatus qs;
now = GNUNET_TIME_absolute_get ();
- expire_deposit = GNUNET_TIME_timestamp_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.abs_time,
DEPOSIT_GRACE_PERIOD);
@@ -829,38 +812,35 @@ sync_denomination (void *cls,
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. */
- TALER_ARL_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_master_pub,
&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);
@@ -877,8 +857,8 @@ 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,
&denom_h);
if (0 > cnt)
@@ -890,39 +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,
&denom_h,
- &ds->denom_balance,
- &ds->denom_loss,
- &ds->denom_risk,
- &ds->denom_recoup,
- ds->num_issued);
+ &ds->dcd);
else
qs = TALER_ARL_adb->insert_denomination_balance (TALER_ARL_adb->cls,
&denom_h,
- &ds->denom_balance,
- &ds->denom_loss,
- &ds->denom_risk,
- &ds->denom_recoup,
- ds->num_issued);
+ &ds->dcd);
}
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
@@ -963,7 +935,7 @@ sync_denomination (void *cls,
static enum GNUNET_GenericReturnValue
withdraw_cb (void *cls,
uint64_t rowid,
- const struct TALER_BlindedCoinHash *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,
@@ -972,9 +944,8 @@ withdraw_cb (void *cls,
{
struct CoinContext *cc = cls;
struct DenominationSummary *ds;
- struct TALER_DenominationHash 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
@@ -985,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,
@@ -1016,29 +988,27 @@ 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.hash),
- TALER_amount2s (&value));
- ds->num_issued++;
- TALER_ARL_amount_add (&ds->denom_balance,
- &ds->denom_balance,
- &value);
+ TALER_amount2s (&issue->value));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"New balance of denomination `%s' is %s\n",
GNUNET_h2s (&dh.hash),
- TALER_amount2s (&ds->denom_balance));
- TALER_ARL_amount_add (&total_escrow_balance,
- &total_escrow_balance,
- &value);
- TALER_ARL_amount_add (&total_risk,
- &total_risk,
- &value);
- TALER_ARL_amount_add (&ds->denom_risk,
- &ds->denom_risk,
- &value);
+ 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;
@@ -1054,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.
@@ -1071,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.
@@ -1086,9 +1056,6 @@ 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,
@@ -1100,7 +1067,7 @@ reveal_data_cb (void *cls,
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++)
@@ -1143,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);
@@ -1197,8 +1161,8 @@ check_known_coin (const char *operation,
loss_potential),
GNUNET_JSON_pack_data_auto ("coin_pub",
coin_pub)));
- TALER_ARL_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
+ 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);
@@ -1207,6 +1171,65 @@ check_known_coin (const char *operation,
/**
+ * 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;
+ }
+ 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));
+}
+
+
+/**
* Function called with details about coins that were melted, with the
* goal of auditing the refresh's execution. Verifies the signature
* and updates our information about coins outstanding (the old coin's
@@ -1216,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
@@ -1223,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,
@@ -1234,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,21 +1302,20 @@ refresh_session_cb (void *cls,
/* verify melt signature */
{
- struct TALER_DenominationHash h_denom_pub;
- struct TALER_Amount fee_refresh;
+ struct TALER_DenominationHashP h_denom_pub;
TALER_denom_pub_hash (denom_pub,
&h_denom_pub);
- TALER_amount_ntoh (&fee_refresh,
- &issue->fee_refresh);
if (GNUNET_OK !=
TALER_wallet_melt_verify (amount_with_fee,
- &fee_refresh,
+ &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,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("operation",
@@ -1302,8 +1326,8 @@ refresh_session_cb (void *cls,
amount_with_fee),
GNUNET_JSON_pack_data_auto ("coin_pub",
coin_pub)));
- TALER_ARL_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_irregular_loss),
+ &TALER_ARL_USE_AB (coin_irregular_loss),
amount_with_fee);
}
}
@@ -1336,7 +1360,7 @@ 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,
+ TALER_ARL_report (report_refreshes_hanging,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_uint64 ("row",
rowid),
@@ -1371,53 +1395,44 @@ refresh_session_cb (void *cls,
&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);
TALER_ARL_amount_add (&refresh_cost,
&refresh_cost,
- &fee);
+ &ni->fees.withdraw);
TALER_ARL_amount_add (&refresh_cost,
&refresh_cost,
- &value);
+ &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 (TALER_ARL_SR_POSITIVE !=
- TALER_ARL_amount_subtract_neg (&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_set_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' */
@@ -1428,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",
@@ -1442,29 +1458,27 @@ 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.hash),
- TALER_amount2s (&value));
- dsi->num_issued++;
- TALER_ARL_amount_add (&dsi->denom_balance,
- &dsi->denom_balance,
- &value);
- TALER_ARL_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.hash),
- TALER_amount2s (&dsi->denom_balance));
- TALER_ARL_amount_add (&total_escrow_balance,
- &total_escrow_balance,
- &value);
- TALER_ARL_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 (reveal_ctx.new_issues);
@@ -1482,58 +1496,15 @@ refresh_session_cb (void *cls,
}
else
{
- if (TALER_ARL_SR_INVALID_NEGATIVE ==
- TALER_ARL_amount_subtract_neg (&tmp,
- &dso->denom_balance,
- amount_with_fee))
- {
- TALER_ARL_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
- {
- TALER_ARL_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.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);
- TALER_ARL_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;
@@ -1561,14 +1532,15 @@ deposit_cb (void *cls,
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) done;
(void) exchange_timestamp;
- GNUNET_assert (rowid >= ppc.last_deposit_serial_id); /* should be monotonically increasing */
- ppc.last_deposit_serial_id = rowid + 1;
+ 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,
@@ -1612,26 +1584,27 @@ deposit_cb (void *cls,
/* Verify deposit signature */
{
- struct TALER_MerchantWireHash h_wire;
- struct TALER_DenominationHash h_denom_pub;
- struct TALER_Amount deposit_fee;
+ struct TALER_MerchantWireHashP h_wire;
+ struct TALER_DenominationHashP h_denom_pub;
TALER_denom_pub_hash (denom_pub,
&h_denom_pub);
TALER_merchant_wire_signature_hash (deposit->receiver_wire_account,
&deposit->wire_salt,
&h_wire);
- TALER_amount_ntoh (&deposit_fee,
- &issue->fee_deposit);
/* 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 !=
TALER_wallet_deposit_verify (&deposit->amount_with_fee,
- &deposit_fee,
+ &issue->fees.deposit,
&h_wire,
&deposit->h_contract_terms,
- NULL /* h_extensions! */,
+ 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,
@@ -1649,8 +1622,8 @@ deposit_cb (void *cls,
&deposit->amount_with_fee),
GNUNET_JSON_pack_data_auto ("coin_pub",
&deposit->coin.coin_pub)));
- TALER_ARL_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
+ 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;
@@ -1675,62 +1648,15 @@ deposit_cb (void *cls,
}
else
{
- struct TALER_Amount tmp;
-
- if (TALER_ARL_SR_INVALID_NEGATIVE ==
- TALER_ARL_amount_subtract_neg (&tmp,
- &ds->denom_balance,
- &deposit->amount_with_fee))
- {
- TALER_ARL_amount_add (&ds->denom_loss,
- &ds->denom_loss,
- &deposit->amount_with_fee);
- ds->report_emergency = GNUNET_YES;
- }
- else
- {
- ds->denom_balance = tmp;
- }
-
- if (-1 == TALER_amount_cmp (&total_escrow_balance,
- &deposit->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,
- &deposit->amount_with_fee,
- 0);
- }
- else
- {
- TALER_ARL_amount_subtract (&total_escrow_balance,
- &total_escrow_balance,
- &deposit->amount_with_fee);
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "New balance of denomination `%s' after deposit is %s\n",
- GNUNET_h2s (&issue->denom_hash.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);
- TALER_ARL_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;
@@ -1751,6 +1677,7 @@ 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
*/
@@ -1761,19 +1688,19 @@ refund_cb (void *cls,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_MerchantSignatureP *merchant_sig,
- const struct TALER_PrivateContractHash *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,
@@ -1812,25 +1739,23 @@ refund_cb (void *cls,
amount_with_fee),
GNUNET_JSON_pack_data_auto ("coin_pub",
coin_pub)));
- TALER_ARL_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
+ 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 (TALER_ARL_SR_INVALID_NEGATIVE ==
TALER_ARL_amount_subtract_neg (&amount_without_fee,
amount_with_fee,
- &refund_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;
@@ -1855,27 +1780,160 @@ refund_cb (void *cls,
}
else
{
- TALER_ARL_amount_add (&ds->denom_balance,
- &ds->denom_balance,
+ TALER_ARL_amount_add (&ds->dcd.denom_balance,
+ &ds->dcd.denom_balance,
&amount_without_fee);
- TALER_ARL_amount_add (&ds->denom_risk,
- &ds->denom_risk,
+ TALER_ARL_amount_add (&ds->dcd.denom_risk,
+ &ds->dcd.denom_risk,
&amount_without_fee);
- TALER_ARL_amount_add (&total_escrow_balance,
- &total_escrow_balance,
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_escrowed),
+ &TALER_ARL_USE_AB (total_escrowed),
&amount_without_fee);
- TALER_ARL_amount_add (&total_risk,
- &total_risk,
+ 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.hash),
- TALER_amount2s (&ds->denom_balance));
+ TALER_amount2s (&ds->dcd.denom_balance));
}
/* update total refund fee balance */
- TALER_ARL_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;
@@ -1904,13 +1962,23 @@ check_recoup (struct CoinContext *cc,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const union 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))
{
@@ -1924,8 +1992,8 @@ check_recoup (struct CoinContext *cc,
amount),
GNUNET_JSON_pack_data_auto ("coin_pub",
&coin->denom_pub_hash)));
- TALER_ARL_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
+ 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,
@@ -1959,29 +2027,6 @@ check_recoup (struct CoinContext *cc,
cc->qs = qs;
return GNUNET_SYSERR;
}
- 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",
- operation),
- 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 (&total_bad_sig_loss,
- &total_bad_sig_loss,
- amount);
- if (TALER_ARL_do_abort ())
- return GNUNET_SYSERR;
- return GNUNET_OK;
- }
ds = get_denomination_summary (cc,
issue,
&issue->denom_hash);
@@ -1993,7 +2038,7 @@ 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,
@@ -2008,15 +2053,15 @@ check_recoup (struct CoinContext *cc,
amount),
GNUNET_JSON_pack_data_auto ("coin_pub",
&coin->coin_pub)));
- TALER_ARL_amount_add (&total_bad_sig_loss,
- &total_bad_sig_loss,
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (coin_irregular_loss),
+ &TALER_ARL_USE_AB (coin_irregular_loss),
amount);
}
- TALER_ARL_amount_add (&ds->denom_recoup,
- &ds->denom_recoup,
+ TALER_ARL_amount_add (&ds->dcd.recoup_loss,
+ &ds->dcd.recoup_loss,
amount);
- TALER_ARL_amount_add (&total_recoup_loss,
- &total_recoup_loss,
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (total_recoup_loss),
+ &TALER_ARL_USE_AB (total_recoup_loss),
amount);
}
if (TALER_ARL_do_abort ())
@@ -2039,7 +2084,7 @@ 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_Timestamp timestamp,
@@ -2048,14 +2093,37 @@ recoup_cb (void *cls,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const union 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,
@@ -2089,20 +2157,20 @@ recoup_refresh_cb (void *cls,
struct GNUNET_TIME_Timestamp timestamp,
const struct TALER_Amount *amount,
const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
- const struct TALER_DenominationHash *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 union 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));
@@ -2137,16 +2205,39 @@ recoup_refresh_cb (void *cls,
}
else
{
- TALER_ARL_amount_add (&dso->denom_balance,
- &dso->denom_balance,
+ 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.hash),
- TALER_amount2s (&dso->denom_balance));
+ 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,
@@ -2166,52 +2257,37 @@ recoup_refresh_cb (void *cls,
*
* @param cls closure, NULL
* @param denom_pub public key, sometimes NULL (!)
- * @param validity issuing information with value, fees and other info about the denomination.
+ * @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_DenominationKeyInformationP *validity)
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue)
{
- const struct TALER_DenominationKeyValidityPS *issue = &validity->properties;
enum GNUNET_DB_QueryStatus qs;
struct TALER_AuditorSignatureP auditor_sig;
- struct TALER_Amount coin_value;
- struct TALER_Amount fee_withdraw;
- struct TALER_Amount fee_deposit;
- struct TALER_Amount fee_refresh;
- struct TALER_Amount fee_refund;
- struct GNUNET_TIME_Timestamp start;
- struct GNUNET_TIME_Timestamp end;
(void) cls;
(void) denom_pub;
- TALER_amount_ntoh (&coin_value,
- &issue->value);
- TALER_amount_ntoh (&fee_withdraw,
- &issue->fee_withdraw);
- TALER_amount_ntoh (&fee_deposit,
- &issue->fee_deposit);
- TALER_amount_ntoh (&fee_refresh,
- &issue->fee_refresh);
- TALER_amount_ntoh (&fee_refund,
- &issue->fee_refund);
- start = GNUNET_TIME_timestamp_ntoh (issue->start);
- end = GNUNET_TIME_timestamp_ntoh (issue->expire_legal);
qs = TALER_ARL_edb->select_auditor_denom_sig (TALER_ARL_edb->cls,
&issue->denom_hash,
&TALER_ARL_auditor_pub,
&auditor_sig);
- if (0 >= qs)
+ 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 (&coin_value),
- GNUNET_TIME_timestamp2s (start),
- (unsigned long long) start.abs_time.abs_value_us,
- (unsigned long long) end.abs_time.abs_value_us);
+ 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 !=
@@ -2219,15 +2295,12 @@ check_denomination (
TALER_ARL_auditor_url,
&issue->denom_hash,
&TALER_ARL_master_pub,
- start,
- GNUNET_TIME_timestamp_ntoh (issue->expire_withdraw),
- GNUNET_TIME_timestamp_ntoh (issue->expire_deposit),
- end,
- &coin_value,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund,
+ issue->start,
+ issue->expire_withdraw,
+ issue->expire_deposit,
+ issue->expire_legal,
+ &issue->value,
+ &issue->fees,
&TALER_ARL_auditor_pub,
&auditor_sig))
{
@@ -2236,12 +2309,134 @@ check_denomination (
GNUNET_JSON_pack_data_auto ("denomination",
&issue->denom_hash),
TALER_JSON_pack_amount ("value",
- &coin_value),
+ &issue->value),
TALER_JSON_pack_time_abs_human ("start_time",
- start.abs_time),
+ issue->start.abs_time),
TALER_JSON_pack_time_abs_human ("end_time",
- end.abs_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;
}
@@ -2272,9 +2467,17 @@ analyze_coins (void *cls)
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Analyzing coins\n");
- qsp = TALER_ARL_adb->get_auditor_progress_coin (TALER_ARL_adb->cls,
- &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);
@@ -2287,29 +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_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);
@@ -2320,7 +2533,7 @@ analyze_coins (void *cls)
if (0 >
(qs = TALER_ARL_edb->select_withdrawals_above_serial_id (
TALER_ARL_edb->cls,
- ppc.last_withdraw_serial_id,
+ TALER_ARL_USE_PP (coins_withdraw_serial_id),
&withdraw_cb,
&cc)) )
{
@@ -2334,7 +2547,7 @@ analyze_coins (void *cls)
if (0 >
(qs = TALER_ARL_edb->select_refunds_above_serial_id (
TALER_ARL_edb->cls,
- ppc.last_refund_serial_id,
+ TALER_ARL_USE_PP (coins_refund_serial_id),
&refund_cb,
&cc)))
{
@@ -2344,11 +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,
- ppc.last_recoup_refresh_serial_id,
+ TALER_ARL_USE_PP (coins_recoup_refresh_serial_id),
&recoup_refresh_cb,
&cc)))
{
@@ -2360,7 +2588,7 @@ analyze_coins (void *cls)
if (0 >
(qs = TALER_ARL_edb->select_recoup_above_serial_id (
TALER_ARL_edb->cls,
- ppc.last_recoup_serial_id,
+ TALER_ARL_USE_PP (coins_recoup_serial_id),
&recoup_cb,
&cc)))
{
@@ -2370,11 +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,
- ppc.last_melt_serial_id,
+ TALER_ARL_USE_PP (coins_melt_serial_id),
&refresh_session_cb,
&cc)))
{
@@ -2386,9 +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,
- ppc.last_deposit_serial_id,
+ TALER_ARL_USE_PP (coins_deposit_serial_id),
&deposit_cb,
&cc)))
{
@@ -2398,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,
@@ -2410,25 +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_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_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);
@@ -2436,13 +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_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_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,
@@ -2451,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;
}
@@ -2502,25 +2767,29 @@ run (void *cls,
&reported_emergency_loss_by_count));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &total_escrow_balance));
+ &TALER_ARL_USE_AB (total_escrowed)));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &total_risk));
+ &TALER_ARL_USE_AB (
+ coin_deposit_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &total_recoup_loss));
+ &TALER_ARL_USE_AB (
+ coin_melt_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &total_irregular_recoups));
+ &TALER_ARL_USE_AB (
+ coin_refund_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &total_deposit_fee_income));
+ &TALER_ARL_USE_AB (coin_balance_risk)));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &total_melt_fee_income));
+ &TALER_ARL_USE_AB (total_recoup_loss)));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &total_refund_fee_income));
+ &TALER_ARL_USE_AB (
+ coin_irregular_loss)));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
&total_arithmetic_delta_plus));
@@ -2529,9 +2798,6 @@ run (void *cls,
&total_arithmetic_delta_minus));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &total_bad_sig_loss));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TALER_ARL_currency,
&total_refresh_hanging));
GNUNET_assert (NULL !=
(report_emergencies = json_array ()));
@@ -2547,7 +2813,7 @@ run (void *cls,
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))
@@ -2560,27 +2826,26 @@ run (void *cls,
TALER_ARL_done (
GNUNET_JSON_PACK (
TALER_JSON_pack_amount ("total_escrow_balance",
- &total_escrow_balance),
- TALER_JSON_pack_amount ("total_active_risk",
- &total_risk),
+ &TALER_ARL_USE_AB (total_escrowed)),
TALER_JSON_pack_amount ("total_deposit_fee_income",
- &total_deposit_fee_income),
+ &TALER_ARL_USE_AB (coin_deposit_fee_revenue)),
TALER_JSON_pack_amount ("total_melt_fee_income",
- &total_melt_fee_income),
+ &TALER_ARL_USE_AB (coin_melt_fee_revenue)),
TALER_JSON_pack_amount ("total_refund_fee_income",
- &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 #4/#5/#6/#13/#26 */
- GNUNET_JSON_pack_array_steal ("bad_sig_losses",
- report_bad_sig_losses),
- /* Tested in test-auditor.sh #4/#5/#6/#13/#26 */
- TALER_JSON_pack_amount ("total_bad_sig_loss",
- &total_bad_sig_loss),
/* Tested in test-auditor.sh #31 */
GNUNET_JSON_pack_array_steal ("row_inconsistencies",
report_row_inconsistencies),
@@ -2593,11 +2858,11 @@ run (void *cls,
&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_refreshs_hanging),
- TALER_JSON_pack_amount ("total_recoup_loss",
- &total_recoup_loss),
+ report_refreshes_hanging),
/* Tested in test-auditor.sh #18 */
GNUNET_JSON_pack_array_steal ("emergencies_by_count",
report_emergencies_by_count),
@@ -2611,38 +2876,48 @@ run (void *cls,
TALER_JSON_pack_amount ("emergencies_loss_by_count",
&reported_emergency_loss_by_count),
GNUNET_JSON_pack_uint64 ("start_ppc_withdraw_serial_id",
- ppc_start.last_withdraw_serial_id),
+ 0 /* not implemented */),
GNUNET_JSON_pack_uint64 ("start_ppc_deposit_serial_id",
- ppc_start.last_deposit_serial_id),
+ 0 /* not implemented */),
GNUNET_JSON_pack_uint64 ("start_ppc_melt_serial_id",
- ppc_start.last_melt_serial_id),
+ 0 /* not implemented */),
GNUNET_JSON_pack_uint64 ("start_ppc_refund_serial_id",
- ppc_start.last_refund_serial_id),
+ 0 /* not implemented */),
GNUNET_JSON_pack_uint64 ("start_ppc_recoup_serial_id",
- ppc_start.last_recoup_serial_id),
+ 0 /* not implemented */),
GNUNET_JSON_pack_uint64 ("start_ppc_recoup_refresh_serial_id",
- ppc_start.
- last_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",
- ppc.last_withdraw_serial_id),
+ TALER_ARL_USE_PP (coins_withdraw_serial_id)),
GNUNET_JSON_pack_uint64 ("end_ppc_deposit_serial_id",
- ppc.last_deposit_serial_id),
+ TALER_ARL_USE_PP (coins_deposit_serial_id)),
GNUNET_JSON_pack_uint64 ("end_ppc_melt_serial_id",
- ppc.last_melt_serial_id),
+ TALER_ARL_USE_PP (coins_melt_serial_id)),
GNUNET_JSON_pack_uint64 ("end_ppc_refund_serial_id",
- ppc.last_refund_serial_id),
+ TALER_ARL_USE_PP (coins_refund_serial_id)),
GNUNET_JSON_pack_uint64 ("end_ppc_recoup_serial_id",
- ppc.last_recoup_serial_id),
+ TALER_ARL_USE_PP (coins_recoup_serial_id)),
GNUNET_JSON_pack_uint64 ("end_ppc_recoup_refresh_serial_id",
- ppc.last_recoup_refresh_serial_id),
- TALER_JSON_pack_time_abs_human ("auditor_start_time",
- start_time),
+ 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 ()),
- TALER_JSON_pack_amount ("total_irregular_recoups",
- &total_irregular_recoups),
- GNUNET_JSON_pack_array_steal ("unsigned_denominations",
- report_denominations_without_sigs)));
+ GNUNET_JSON_pack_array_steal (
+ "unsigned_denominations",
+ report_denominations_without_sigs)));
}
@@ -2662,11 +2937,10 @@ main (int argc,
"internal",
"perform checks only applicable for exchange-internal audits",
&internal_checks),
- 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 ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
GNUNET_GETOPT_option_timetravel ('T',
"timetravel"),
GNUNET_GETOPT_OPTION_END
diff --git a/src/auditor/taler-helper-auditor-deposits.c b/src/auditor/taler-helper-auditor-deposits.c
index 2499df2eb..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-2021 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;
@@ -56,6 +87,18 @@ static struct TALER_Amount total_missed_deposit_confirmations;
*/
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.
*/
@@ -92,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.
@@ -102,14 +144,16 @@ struct DepositConfirmationContext
* @param dc the deposit confirmation we know
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop iterating
*/
-static int
+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 GNUNET_TIME_Timestamp exchange_timestamp;
@@ -118,20 +162,12 @@ test_dc (void *cls,
qs = TALER_ARL_edb->have_deposit2 (TALER_ARL_edb->cls,
&dc->h_contract_terms,
&dc->h_wire,
- &dc->coin_pub,
+ &dc->coin_pubs[i],
&dc->merchant,
dc->refund_deadline,
&deposit_fee,
&exchange_timestamp);
- if (qs > 0)
- {
- 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; /* found, all good */
- }
+ missing |= (0 == qs);
if (qs < 0)
{
GNUNET_break (0); /* DB error, complain */
@@ -139,6 +175,15 @@ test_dc (void *cls,
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,
@@ -146,7 +191,7 @@ test_dc (void *cls,
TALER_JSON_pack_time_abs_human ("timestamp",
dc->exchange_timestamp.abs_time),
TALER_JSON_pack_amount ("amount",
- &dc->amount_without_fee),
+ &dc->total_without_fee),
GNUNET_JSON_pack_uint64 ("rowid",
serial_id),
GNUNET_JSON_pack_data_auto ("account",
@@ -156,7 +201,7 @@ test_dc (void *cls,
dcc->missed_count++;
TALER_ARL_amount_add (&dcc->missed_amount,
&dcc->missed_amount,
- &dc->amount_without_fee);
+ &dc->total_without_fee);
if (TALER_ARL_do_abort ())
return GNUNET_SYSERR;
return GNUNET_OK;
@@ -173,20 +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_master_pub,
- &ppdc);
+ TALER_ARL_GET_PP (deposit_confirmation_serial_id),
+ NULL);
+
if (0 > qsp)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qsp);
@@ -201,7 +244,8 @@ 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' */
@@ -211,10 +255,13 @@ analyze_deposit_confirmations (void *cls)
dcc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
dcc.missed_count = 0LLU;
dcc.first_missed_coin_serial = UINT64_MAX;
+ 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_master_pub,
- ppdc.last_deposit_confirmation_serial_id,
+ TALER_ARL_USE_PP (deposit_confirmation_serial_id),
+ true, /* return suppressed */
&test_dc,
&dcc);
if (0 > qsx)
@@ -225,28 +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_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_master_pub,
- &ppdc);
+ TALER_ARL_SET_PP (deposit_confirmation_serial_id),
+ NULL);
if (0 >= qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -254,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
@@ -281,6 +403,10 @@ 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 !=
@@ -289,6 +415,34 @@ run (void *cls,
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 !=
@@ -333,11 +487,10 @@ main (int argc,
"internal",
"perform checks only applicable for exchange-internal audits",
&internal_checks),
- 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 ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
GNUNET_GETOPT_option_timetravel ('T',
"timetravel"),
GNUNET_GETOPT_OPTION_END
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 610193187..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-2021 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;
@@ -205,8 +214,8 @@ report_amount_arithmetic_inconsistency (
if (0 != profitable)
{
target = (1 == profitable)
- ? &total_arithmetic_delta_plus
- : &total_arithmetic_delta_minus;
+ ? &total_arithmetic_delta_plus
+ : &total_arithmetic_delta_minus;
TALER_ARL_amount_add (target,
target,
&delta);
@@ -264,21 +273,15 @@ 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 balance_at_previous_audit;
-
- /**
- * 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.
@@ -297,7 +300,7 @@ struct ReserveSummary
* #load_auditor_reserve_summary() together with the a-* values
* (if available).
*/
- int had_ri;
+ bool had_ri;
};
@@ -318,10 +321,8 @@ load_auditor_reserve_summary (struct ReserveSummary *rs)
qs = TALER_ARL_adb->get_reserve_info (TALER_ARL_adb->cls,
&rs->reserve_pub,
- &TALER_ARL_master_pub,
&rowid,
- &rs->balance_at_previous_audit,
- &rs->a_withdraw_fee_balance,
+ &rs->prev_balance,
&rs->a_expiration_date,
&rs->sender_account);
if (0 > qs)
@@ -331,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->balance_at_previous_audit));
+ &rs->prev_balance.close_fee_balance));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (rs->total_in.currency,
- &rs->a_withdraw_fee_balance));
+ &rs->prev_balance.purse_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (rs->total_in.currency,
+ &rs->prev_balance.open_fee_balance));
+ GNUNET_assert (GNUNET_OK ==
+ 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->balance_at_previous_audit));
+ "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->balance_at_previous_audit,
- &rs->a_withdraw_fee_balance)) ||
- (GNUNET_YES !=
- TALER_amount_cmp_currency (&rs->total_in,
- &rs->balance_at_previous_audit)) )
- {
- 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->balance_at_previous_audit));
+ TALER_amount2s (&rs->prev_balance.reserve_balance));
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
@@ -390,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`
@@ -401,7 +472,7 @@ 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,
@@ -411,54 +482,22 @@ handle_reserve_in (void *cls,
struct GNUNET_TIME_Timestamp execution_date)
{
struct ReserveContext *rc = cls;
- struct GNUNET_HashCode key;
struct ReserveSummary *rs;
struct GNUNET_TIME_Timestamp expiry;
- enum GNUNET_DB_QueryStatus qs;
(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_set_zero (credit->currency,
- &rs->total_out));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_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
- {
- TALER_ARL_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),
@@ -468,6 +507,9 @@ handle_reserve_in (void *cls,
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;
@@ -491,7 +533,7 @@ handle_reserve_in (void *cls,
static enum GNUNET_GenericReturnValue
handle_reserve_out (void *cls,
uint64_t rowid,
- const struct TALER_BlindedCoinHash *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,
@@ -499,31 +541,21 @@ handle_reserve_out (void *cls,
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 TALER_Amount auditor_value;
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
struct TALER_Amount auditor_amount_with_fee;
- struct GNUNET_TIME_Timestamp valid_start;
- struct GNUNET_TIME_Timestamp expire_withdraw;
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);
@@ -546,17 +578,15 @@ handle_reserve_out (void *cls,
}
/* check that execution date is within withdraw range for denom_pub */
- valid_start = GNUNET_TIME_timestamp_ntoh (issue->start);
- expire_withdraw = GNUNET_TIME_timestamp_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_time.abs_value_us,
- (unsigned long long) expire_withdraw.abs_time.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 (valid_start,
+ if (GNUNET_TIME_timestamp_cmp (issue->start,
>,
execution_date) ||
- GNUNET_TIME_timestamp_cmp (expire_withdraw,
+ GNUNET_TIME_timestamp_cmp (issue->expire_withdraw,
<,
execution_date))
{
@@ -569,17 +599,16 @@ handle_reserve_out (void *cls,
GNUNET_JSON_pack_data_auto ("reserve_pub",
reserve_pub),
GNUNET_JSON_pack_data_auto ("denompub_h",
- &wsrd.h_denomination_pub)));
+ &h_denom_pub)));
}
/* check reserve_sig (first: setup remaining members of wsrd) */
- TALER_amount_hton (&wsrd.amount_with_fee,
- amount_with_fee);
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
- &wsrd,
- &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,
GNUNET_JSON_PACK (
@@ -596,16 +625,12 @@ handle_reserve_out (void *cls,
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 */
+ return GNUNET_OK; /* exit function here, we cannot add this to the legitimate withdrawals */
}
- TALER_amount_ntoh (&withdraw_fee,
- &issue->fee_withdraw);
- TALER_amount_ntoh (&auditor_value,
- &issue->value);
TALER_ARL_amount_add (&auditor_amount_with_fee,
- &auditor_value,
- &withdraw_fee);
+ &issue->value,
+ &issue->fees.withdraw);
if (0 !=
TALER_amount_cmp (&auditor_amount_with_fee,
amount_with_fee))
@@ -614,43 +639,12 @@ handle_reserve_out (void *cls,
rowid,
"amount with fee from exchange does not match denomination value plus fee");
}
-
-
- 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 = auditor_amount_with_fee;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (amount_with_fee->currency,
- &rs->total_in));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_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));
- }
- else
- {
- TALER_ARL_amount_add (&rs->total_out,
- &rs->total_out,
- &auditor_amount_with_fee);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Reserve `%s' reduced by %s from withdraw\n",
@@ -658,10 +652,16 @@ handle_reserve_out (void *cls,
TALER_amount2s (&auditor_amount_with_fee));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Increasing withdraw profits by fee %s\n",
- TALER_amount2s (&withdraw_fee));
- TALER_ARL_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;
@@ -694,10 +694,9 @@ handle_recoup_by_reserve (
const struct TALER_CoinPublicInfo *coin,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const union 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_Timestamp expiry;
struct TALER_MasterSignatureP msig;
@@ -707,8 +706,8 @@ 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 !=
@@ -752,8 +751,8 @@ handle_recoup_by_reserve (
report_row_inconsistency ("recoup",
rowid,
"denomination key not in revocation set");
- TALER_ARL_amount_add (&total_irregular_recoups,
- &total_irregular_recoups,
+ TALER_ARL_amount_add (&TALER_ARL_USE_AB (reserves_reserve_loss),
+ &TALER_ARL_USE_AB (reserves_reserve_loss),
amount);
}
else
@@ -770,20 +769,22 @@ handle_recoup_by_reserve (
{
rev = "revoked";
}
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_put (rc->revoked,
- &coin->denom_pub_hash.
- 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,
GNUNET_JSON_PACK (
@@ -800,42 +801,16 @@ handle_recoup_by_reserve (
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_set_zero (amount->currency,
- &rs->total_out));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_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
- {
- TALER_ARL_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),
@@ -868,7 +843,7 @@ get_closing_fee (const char *receiver_account,
struct TALER_MasterSignatureP master_sig;
struct GNUNET_TIME_Timestamp start_date;
struct GNUNET_TIME_Timestamp end_date;
- struct TALER_Amount wire_fee;
+ struct TALER_WireFeeSet fees;
char *method;
method = TALER_payto_get_method (receiver_account);
@@ -881,8 +856,7 @@ get_closing_fee (const char *receiver_account,
atime,
&start_date,
&end_date,
- &wire_fee,
- fee,
+ &fees,
&master_sig))
{
char *diag;
@@ -898,12 +872,95 @@ get_closing_fee (const char *receiver_account,
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.
*
@@ -915,9 +972,11 @@ 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,
@@ -926,56 +985,27 @@ handle_reserve_closed (
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_set_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;
- TALER_ARL_amount_add (&rs->total_out,
- &rs->total_out,
- amount_with_fee);
- TALER_ARL_amount_add (&rs->total_fee,
- &rs->total_fee,
- closing_fee);
/* verify closing_fee is correct! */
if (GNUNET_OK !=
get_closing_fee (receiver_account,
@@ -995,20 +1025,117 @@ 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),
@@ -1020,6 +1147,137 @@ handle_reserve_closed (
/**
+ * 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;
+}
+
+
+/**
* Check that the reserve summary matches what the exchange database
* thinks about the reserve, and update our own state of the reserve.
*
@@ -1030,36 +1288,39 @@ 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_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) */
- TALER_ARL_amount_add (&balance,
+ TALER_ARL_amount_add (&mbalance,
&rs->total_in,
- &rs->balance_at_previous_audit);
+ &rs->prev_balance.reserve_balance);
if (TALER_ARL_SR_INVALID_NEGATIVE ==
TALER_ARL_amount_subtract_neg (&nbalance,
- &balance,
+ &mbalance,
&rs->total_out))
{
struct TALER_Amount loss;
TALER_ARL_amount_subtract (&loss,
&rs->total_out,
- &balance);
- TALER_ARL_amount_add (&total_balance_insufficient_loss,
- &total_balance_insufficient_loss,
+ &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,
GNUNET_JSON_PACK (
@@ -1069,8 +1330,13 @@ verify_reserve_balance (void *cls,
&loss)));
/* Continue with a reserve balance of zero */
GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (balance.currency,
- &nbalance));
+ TALER_amount_set_zero (TALER_ARL_currency,
+ &rs->curr_balance.reserve_balance));
+ }
+ else
+ {
+ /* Update remaining reserve balance! */
+ rs->curr_balance.reserve_balance = nbalance;
}
if (internal_checks)
@@ -1080,13 +1346,10 @@ verify_reserve_balance (void *cls,
internal audit, as otherwise the balance of the 'reserves' table
is not replicated at the auditor. */
struct TALER_EXCHANGEDB_Reserve reserve;
- struct TALER_EXCHANGEDB_KycStatus kyc;
reserve.pub = rs->reserve_pub;
qs = TALER_ARL_edb->reserves_get (TALER_ARL_edb->cls,
- &reserve,
- &kyc);
- // FIXME: figure out what to do with KYC status!
+ &reserve);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
/* If the exchange doesn't have this reserve in the summary, it
@@ -1094,7 +1357,7 @@ verify_reserve_balance (void *cls,
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. *///
+ restoring the summary. */
TALER_ARL_report (report_reserve_balance_insufficient_inconsistencies,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("reserve_pub",
@@ -1111,17 +1374,17 @@ verify_reserve_balance (void *cls,
else
{
/* Check that exchange's balance matches our expected balance for the reserve */
- if (0 != TALER_amount_cmp (&nbalance,
+ if (0 != TALER_amount_cmp (&rs->curr_balance.reserve_balance,
&reserve.balance))
{
struct TALER_Amount delta;
- if (0 < TALER_amount_cmp (&nbalance,
+ if (0 < TALER_amount_cmp (&rs->curr_balance.reserve_balance,
&reserve.balance))
{
/* balance > reserve.balance */
TALER_ARL_amount_subtract (&delta,
- &nbalance,
+ &rs->curr_balance.reserve_balance,
&reserve.balance);
TALER_ARL_amount_add (&total_balance_summary_delta_plus,
&total_balance_summary_delta_plus,
@@ -1132,7 +1395,7 @@ verify_reserve_balance (void *cls,
/* balance < reserve.balance */
TALER_ARL_amount_subtract (&delta,
&reserve.balance,
- &nbalance);
+ &rs->curr_balance.reserve_balance);
TALER_ARL_amount_add (&total_balance_summary_delta_minus,
&total_balance_summary_delta_minus,
&delta);
@@ -1144,10 +1407,11 @@ verify_reserve_balance (void *cls,
TALER_JSON_pack_amount ("exchange",
&reserve.balance),
TALER_JSON_pack_amount ("auditor",
- &nbalance)));
+ &rs->curr_balance.
+ reserve_balance)));
}
}
- } /* end of 'if (internal_checks)' */
+ } /* 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) */
@@ -1204,26 +1468,43 @@ verify_reserve_balance (void *cls,
}
}
- /* 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));
- TALER_ARL_amount_add (&rs->a_withdraw_fee_balance,
- &rs->a_withdraw_fee_balance,
- &rs->total_fee);
- TALER_ARL_amount_add (&total_escrow_balance,
- &total_escrow_balance,
+ /* 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);
- TALER_ARL_amount_add (&total_withdraw_fee_income,
- &total_withdraw_fee_income,
- &rs->total_fee);
{
struct TALER_Amount r;
if (TALER_ARL_SR_INVALID_NEGATIVE ==
TALER_ARL_amount_subtract_neg (&r,
- &total_escrow_balance,
+ &TALER_ARL_USE_AB (
+ reserves_reserve_total_balance),
&rs->total_out))
{
/* We could not reduce our total balance, i.e. exchange allowed IN TOTAL (!)
@@ -1231,34 +1512,33 @@ verify_reserve_balance (void *cls,
went negative!). Woopsie. Calculate how badly it went and log. */
report_amount_arithmetic_inconsistency ("global escrow balance",
0,
- &total_escrow_balance, /* what we had */
- &rs->total_out, /* what we needed */
+ &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_set_zero (TALER_ARL_currency,
- &total_escrow_balance));
+ &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,
- &rs->reserve_pub,
- &TALER_ARL_master_pub);
+ &rs->reserve_pub);
if (0 >= qs)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
@@ -1269,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
@@ -1280,20 +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,
&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,
&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)
@@ -1303,7 +1578,7 @@ 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,
@@ -1331,9 +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_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);
@@ -1346,19 +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_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);
@@ -1368,10 +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,
- ppr.last_reserve_in_serial_id,
+ TALER_ARL_USE_PP (reserves_reserve_in_serial_id),
&handle_reserve_in,
&rc);
if (qs < 0)
@@ -1381,7 +1680,7 @@ analyze_reserves (void *cls)
}
qs = TALER_ARL_edb->select_withdrawals_above_serial_id (
TALER_ARL_edb->cls,
- ppr.last_reserve_out_serial_id,
+ TALER_ARL_USE_PP (reserves_reserve_out_serial_id),
&handle_reserve_out,
&rc);
if (qs < 0)
@@ -1391,7 +1690,7 @@ analyze_reserves (void *cls)
}
qs = TALER_ARL_edb->select_recoup_above_serial_id (
TALER_ARL_edb->cls,
- ppr.last_reserve_recoup_serial_id,
+ TALER_ARL_USE_PP (reserves_reserve_recoup_serial_id),
&handle_recoup_by_reserve,
&rc);
if (qs < 0)
@@ -1399,9 +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,
- ppr.last_reserve_close_serial_id,
+ TALER_ARL_USE_PP (reserves_reserve_close_serial_id),
&handle_reserve_closed,
&rc);
if (qs < 0)
@@ -1409,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);
@@ -1417,23 +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_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_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)
{
@@ -1441,13 +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_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_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,
@@ -1456,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;
}
@@ -1506,16 +1877,33 @@ run (void *cls,
"Starting audit\n");
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &total_escrow_balance));
+ &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_set_zero (TALER_ARL_currency,
+ &TALER_ARL_USE_AB (
+ reserves_close_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &total_irregular_recoups));
+ &TALER_ARL_USE_AB (
+ reserves_purse_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &total_withdraw_fee_income));
+ &TALER_ARL_USE_AB (
+ reserves_open_fee_revenue)));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &total_balance_insufficient_loss));
+ &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));
@@ -1534,6 +1922,7 @@ run (void *cls,
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 !=
@@ -1546,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 !=
@@ -1562,12 +1954,6 @@ run (void *cls,
}
TALER_ARL_done (
GNUNET_JSON_PACK (
- GNUNET_JSON_pack_array_steal (
- "reserve_balance_insufficient_inconsistencies",
- report_reserve_balance_insufficient_inconsistencies),
- /* Tested in test-auditor.sh #3 */
- TALER_JSON_pack_amount ("total_loss_balance_insufficient",
- &total_balance_insufficient_loss),
/* Tested in test-auditor.sh #3 */
GNUNET_JSON_pack_array_steal (
"reserve_balance_summary_wrong_inconsistencies",
@@ -1576,23 +1962,49 @@ run (void *cls,
&total_balance_summary_delta_plus),
TALER_JSON_pack_amount ("total_balance_summary_delta_minus",
&total_balance_summary_delta_minus),
- /* blocks #2 */
+ /* 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",
- &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",
- &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 #21 */
- TALER_JSON_pack_amount ("total_balance_reserve_not_closed",
- &total_balance_reserve_not_closed),
/* Tested in test-auditor.sh #7 */
GNUNET_JSON_pack_array_steal ("bad_sig_losses",
report_bad_sig_losses),
- /* Tested in test-auditor.sh #7 */
- TALER_JSON_pack_amount ("total_bad_sig_loss",
- &total_bad_sig_loss),
/* Tested in test-revocation.sh #4 */
GNUNET_JSON_pack_array_steal ("row_inconsistencies",
report_row_inconsistencies),
@@ -1602,32 +2014,52 @@ run (void *cls,
denomination_key_validity_withdraw_inconsistencies),
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),
+
+ /* 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 ()),
- TALER_JSON_pack_amount ("total_irregular_recoups",
- &total_irregular_recoups),
GNUNET_JSON_pack_uint64 ("start_ppr_reserve_in_serial_id",
- ppr_start.last_reserve_in_serial_id),
+ 0 /* no longer supported */),
GNUNET_JSON_pack_uint64 ("start_ppr_reserve_out_serial_id",
- ppr_start.last_reserve_out_serial_id),
+ 0 /* no longer supported */),
GNUNET_JSON_pack_uint64 ("start_ppr_reserve_recoup_serial_id",
- ppr_start.last_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",
- ppr_start.last_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",
- ppr.last_reserve_in_serial_id),
+ TALER_ARL_USE_PP (
+ reserves_reserve_in_serial_id)),
GNUNET_JSON_pack_uint64 ("end_ppr_reserve_out_serial_id",
- ppr.last_reserve_out_serial_id),
+ TALER_ARL_USE_PP (
+ reserves_reserve_out_serial_id)),
GNUNET_JSON_pack_uint64 ("end_ppr_reserve_recoup_serial_id",
- ppr.last_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",
- ppr.last_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))));
}
@@ -1647,11 +2079,10 @@ main (int argc,
"internal",
"perform checks only applicable for exchange-internal audits",
&internal_checks),
- 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 ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
GNUNET_GETOPT_option_timetravel ('T',
"timetravel"),
GNUNET_GETOPT_OPTION_END
diff --git a/src/auditor/taler-helper-auditor-wire.c b/src/auditor/taler-helper-auditor-wire.c
index 89758c2fc..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-2021 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
@@ -93,14 +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 wire_off_in;
+
+ /**
+ * Where we are in the outbound transaction history.
+ */
+ uint64_t wire_off_out;
+
+ /**
+ * Label under which we store our pp's reserve_in_serial_id.
+ */
+ char *label_reserve_in_serial_id;
+
+ /**
+ * Label under which we store our pp's reserve_in_serial_id.
*/
- uint64_t in_wire_off;
+ char *label_wire_out_serial_id;
/**
- * Where we are in the inbound (DEBIT) transaction history.
+ * Label under which we store our wire_off_in.
*/
- uint64_t out_wire_off;
+ 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.
@@ -183,20 +217,17 @@ static enum GNUNET_DB_QueryStatus qsx_gwap;
/**
* Last reserve_in / wire_out serial IDs seen.
*/
-static struct TALER_AUDITORDB_WireProgressPoint pp;
-
-/**
- * Last reserve_in / wire_out serial IDs seen.
- */
-static struct TALER_AUDITORDB_WireProgressPoint start_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);
/**
- * 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;
@@ -204,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.
@@ -228,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;
@@ -267,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.
@@ -285,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;
@@ -304,6 +375,12 @@ static struct GNUNET_CURL_RescheduleContext *rc;
*/
static int internal_checks;
+/**
+ * Should we ignore if the bank does not know our bank
+ * account?
+ */
+static int ignore_account_404;
+
/* ***************************** Shutdown **************************** */
/**
@@ -361,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)
@@ -386,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)
@@ -411,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)
@@ -463,11 +540,11 @@ do_shutdown (void *cls)
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 ("missattribution_in_inconsistencies",
- report_missattribution_in_inconsistencies),
+ GNUNET_JSON_pack_array_steal ("misattribution_in_inconsistencies",
+ report_misattribution_in_inconsistencies),
/* Tested in test-auditor.sh #9 */
- TALER_JSON_pack_amount ("total_missattribution_in",
- &total_missattribution_in),
+ 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 */
@@ -479,12 +556,24 @@ do_shutdown (void *cls)
/* 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),
@@ -495,22 +584,28 @@ do_shutdown (void *cls)
start_time),
TALER_JSON_pack_time_abs_human ("wire_auditor_end_time",
GNUNET_TIME_absolute_get ()),
- GNUNET_JSON_pack_uint64 ("start_pp_reserve_close_uuid",
- start_pp.last_reserve_close_uuid),
- GNUNET_JSON_pack_uint64 ("end_pp_reserve_close_uuid",
- pp.last_reserve_close_uuid),
- TALER_JSON_pack_time_abs_human ("start_pp_last_timestamp",
- start_pp.last_timestamp.abs_time),
- TALER_JSON_pack_time_abs_human ("end_pp_last_timestamp",
- pp.last_timestamp.abs_time),
+ 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;
@@ -558,6 +653,10 @@ do_shutdown (void *cls)
GNUNET_CONTAINER_DLL_remove (wa_head,
wa_tail,
wa);
+ 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)
@@ -585,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)
@@ -612,8 +711,8 @@ check_pending_rc (void *cls,
&rc->wtid),
GNUNET_JSON_pack_string ("account",
rc->receiver_account)));
- pp.last_reserve_close_uuid
- = GNUNET_MIN (pp.last_reserve_close_uuid,
+ TALER_ARL_USE_PP (wire_reserve_close_id)
+ = GNUNET_MIN (TALER_ARL_USE_PP (wire_reserve_close_id),
rc->rowid);
return GNUNET_OK;
}
@@ -635,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);
@@ -656,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)
@@ -684,26 +819,33 @@ commit (enum GNUNET_DB_QueryStatus qs)
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),
+ 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_master_pub,
- wa->ai->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_master_pub,
- wa->ai->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,
@@ -716,13 +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_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_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,
@@ -731,8 +879,9 @@ commit (enum GNUNET_DB_QueryStatus qs)
return qs;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Concluded audit step at %s\n",
- GNUNET_TIME_timestamp2s (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)
{
@@ -769,74 +918,341 @@ commit (enum GNUNET_DB_QueryStatus qs)
/* ***************************** Analyze required transfers ************************ */
/**
- * Function called on deposits that are past their due date
- * and have not yet seen a wire transfer.
+ * 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
- * @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 payto_uri 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?
- * NOTE: only valid in internal audit mode!
- * @param done did the exchange claim that it made a transfer?
- * NOTE: only valid in internal audit mode!
+ * @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
-wire_missing_cb (void *cls,
- uint64_t rowid,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount,
- const char *payto_uri,
- struct GNUNET_TIME_Timestamp deadline,
- bool tiny,
- bool done)
+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)
{
- json_t *rep;
+ struct ImportMissingWireContext *wc = cls;
+ enum GNUNET_DB_QueryStatus qs;
- (void) cls;
+ 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,
- amount);
- if (internal_checks)
+ &rd->total_amount);
+ if (NULL != rd->kyc_pending)
{
- /* In internal mode, we insist that the entry was
- actually marked as tiny. */
- if (tiny &&
- (0 > TALER_amount_cmp (amount,
- &tiny_amount)) )
- return; /* acceptable, amount was tiny */
+ 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
{
- /* External auditors do not replicate tiny, so they
- only check that the amount is tiny */
- if (0 > TALER_amount_cmp (amount,
- &tiny_amount))
- return; /* acceptable, amount was tiny */
+ 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);
}
- rep = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_uint64 ("row",
- rowid),
- TALER_JSON_pack_amount ("amount",
- amount),
- TALER_JSON_pack_time_abs_human ("deadline",
- deadline.abs_time),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- coin_pub),
- GNUNET_JSON_pack_string ("account",
- payto_uri));
- if (internal_checks)
+
+ 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
{
- /* the 'done' bit is only useful in 'internal' mode */
- GNUNET_assert (0 ==
- json_object_set (rep,
- "claimed_done",
- json_string ((done) ? "yes" : "no")));
+ TALER_ARL_amount_add (&rd->total_amount,
+ &rd->total_amount,
+ total_amount);
+ rd->deadline = GNUNET_TIME_timestamp_min (rd->deadline,
+ deadline);
}
- TALER_ARL_report (report_lags,
- rep);
+}
+
+
+/**
+ * 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
+ */
+static void
+clear_finished_transfer_cb (
+ void *cls,
+ uint64_t tracking_serial_id,
+ uint64_t batch_deposit_serial_id)
+{
+ 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;
}
@@ -847,30 +1263,78 @@ wire_missing_cb (void *cls,
static void
check_for_required_transfers (void)
{
- struct GNUNET_TIME_Timestamp 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
+ };
+ 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_to_timestamp (
- GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (),
- GRACE_PERIOD));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Analyzing exchange's unfinished deposits (deadline: %s)\n",
- GNUNET_TIME_timestamp2s (next_timestamp));
- qs = TALER_ARL_edb->select_deposits_missing_wire (TALER_ARL_edb->cls,
- 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);
+ 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 ();
@@ -965,6 +1429,9 @@ wire_out_cb (void *cls,
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);
@@ -974,7 +1441,7 @@ 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,
@@ -1142,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)
@@ -1172,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)
@@ -1204,6 +1672,131 @@ complain_out_not_found (void *cls,
&ctx);
if (ctx.found)
return GNUNET_OK;
+ /* 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 (
@@ -1250,6 +1843,7 @@ 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->ai->section_name);
@@ -1283,90 +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->ai->section_name,
- http_status_code,
- (unsigned int) ec);
- commit (GNUNET_DB_STATUS_HARD_ERROR);
- global_ret = EXIT_FAILURE;
- 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_TIME_timestamp2s (details->execution_date),
- TALER_amount2s (&details->amount),
- TALER_B2S (&details->wtid));
- /* Update offset */
- wa->out_wire_off = row_off;
- slen = strlen (details->credit_account_uri) + 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_uri = (const char *) &roi[1];
- memcpy (&roi[1],
- details->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,
- &details->amount);
- TALER_ARL_report (report_wire_format_inconsistencies,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("amount",
- &details->amount),
- GNUNET_JSON_pack_uint64 ("wire_offset",
- row_off),
- GNUNET_JSON_pack_string ("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 ();
}
@@ -1397,10 +1993,12 @@ process_debits (void *cls)
"Checking bank DEBIT records of account `%s'\n",
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->ai->auth,
- wa->out_wire_off,
- INT64_MAX,
+ wa->wire_off_out,
+ INT32_MAX,
GNUNET_TIME_UNIT_ZERO,
&history_debit_cb,
wa);
@@ -1423,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);
}
@@ -1439,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 ();
}
@@ -1459,7 +2061,7 @@ 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,
@@ -1478,6 +2080,9 @@ reserve_in_cb (void *cls,
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;
@@ -1485,9 +2090,9 @@ reserve_in_cb (void *cls,
rii->details.execution_date = execution_date;
rii->details.reserve_pub = *reserve_pub;
rii->details.debit_account_uri = (const char *) &rii[1];
- memcpy (&rii[1],
- sender_account_details,
- slen);
+ GNUNET_memcpy (&rii[1],
+ sender_account_details,
+ slen);
GNUNET_CRYPTO_hash (&wire_reference,
sizeof (uint64_t),
&rii->row_off_hash);
@@ -1503,7 +2108,7 @@ reserve_in_cb (void *cls,
"reserves_in"),
GNUNET_JSON_pack_uint64 ("row",
rowid),
- GNUNET_JSON_pack_data_auto ("wire_offset_hash",
+ GNUNET_JSON_pack_data_auto ("id",
&rii->row_off_hash),
GNUNET_JSON_pack_string ("diagnostic",
"duplicate wire offset")));
@@ -1527,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)
@@ -1571,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/%s!\n",
- wa->ai->section_name,
- http_status,
- TALER_ErrorCode_get_hint (ec));
- commit (GNUNET_DB_STATUS_HARD_ERROR);
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- return GNUNET_SYSERR;
- }
- /* end of operation */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Reconciling CREDIT processing of account `%s'\n",
- wa->ai->section_name);
GNUNET_CONTAINER_multihashmap_iterate (in_map,
&complain_in_not_found,
wa);
@@ -1622,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_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);
@@ -1640,13 +2230,12 @@ 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_TIME_timestamp2s (details->execution_date));
- wa->chh = NULL;
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))
@@ -1657,7 +2246,7 @@ history_credit_cb (void *cls,
GNUNET_JSON_pack_uint64 ("row",
rii->rowid),
GNUNET_JSON_pack_uint64 ("bank_row",
- row_off),
+ details->serial_id),
TALER_JSON_pack_amount ("amount_exchange_expected",
&rii->details.amount),
TALER_JSON_pack_amount ("amount_wired",
@@ -1677,7 +2266,7 @@ history_credit_cb (void *cls,
GNUNET_JSON_pack_uint64 ("row",
rii->rowid),
GNUNET_JSON_pack_uint64 ("bank_row",
- row_off),
+ details->serial_id),
TALER_JSON_pack_amount ("amount_exchange_expected",
&zero),
TALER_JSON_pack_amount ("amount_wired",
@@ -1703,7 +2292,7 @@ history_credit_cb (void *cls,
GNUNET_JSON_pack_uint64 ("row",
rii->rowid),
GNUNET_JSON_pack_uint64 ("bank_row",
- row_off),
+ details->serial_id),
TALER_JSON_pack_amount ("amount_exchange_expected",
&rii->details.amount),
TALER_JSON_pack_amount ("amount_wired",
@@ -1744,19 +2333,19 @@ history_credit_cb (void *cls,
if (0 != strcasecmp (details->debit_account_uri,
rii->details.debit_account_uri))
{
- TALER_ARL_report (report_missattribution_in_inconsistencies,
+ 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",
- row_off),
+ details->serial_id),
GNUNET_JSON_pack_data_auto (
"reserve_pub",
&rii->details.reserve_pub)));
- TALER_ARL_amount_add (&total_missattribution_in,
- &total_missattribution_in,
+ TALER_ARL_amount_add (&total_misattribution_in,
+ &total_misattribution_in,
&rii->details.amount);
}
if (GNUNET_TIME_timestamp_cmp (details->execution_date,
@@ -1770,7 +2359,7 @@ history_credit_cb (void *cls,
GNUNET_JSON_pack_uint64 ("row",
rii->rowid),
GNUNET_JSON_pack_uint64 ("bank_row",
- row_off),
+ details->serial_id),
GNUNET_JSON_pack_string ("diagnostic",
"execution date mismatch")));
}
@@ -1779,7 +2368,60 @@ cleanup:
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 ();
}
@@ -1828,10 +2470,12 @@ process_credits (void *cls)
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Starting bank CREDIT history of account `%s'\n",
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->ai->auth,
- wa->in_wire_off,
- INT64_MAX,
+ wa->wire_off_in,
+ INT32_MAX,
GNUNET_TIME_UNIT_ZERO,
&history_credit_cb,
wa);
@@ -1853,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 */
@@ -1861,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
@@ -1872,9 +2516,11 @@ 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_Timestamp execution_date,
@@ -1882,12 +2528,14 @@ reserve_closed_cb (void *cls,
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 (TALER_ARL_SR_INVALID_NEGATIVE ==
TALER_ARL_amount_subtract_neg (&rc->amount,
@@ -1900,7 +2548,7 @@ reserve_closed_cb (void *cls,
"reserves_closures"),
GNUNET_JSON_pack_uint64 ("row",
rowid),
- GNUNET_JSON_pack_data_auto ("reserve_pub",
+ GNUNET_JSON_pack_data_auto ("id",
reserve_pub),
TALER_JSON_pack_amount ("amount_with_fee",
amount_with_fee),
@@ -1913,8 +2561,8 @@ reserve_closed_cb (void *cls,
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;
@@ -1941,6 +2589,8 @@ reserve_closed_cb (void *cls,
static enum GNUNET_DB_QueryStatus
begin_transaction (void)
{
+ enum GNUNET_DB_QueryStatus qs;
+
if (GNUNET_SYSERR ==
TALER_ARL_edb->preflight (TALER_ARL_edb->cls))
{
@@ -1969,17 +2619,66 @@ begin_transaction (void)
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_master_pub,
- wa->ai->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);
@@ -1987,9 +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_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);
@@ -2002,11 +2704,11 @@ begin_transaction (void)
}
else
{
- start_pp = pp;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming wire audit at %s / %llu\n",
- GNUNET_TIME_timestamp2s (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));
}
{
@@ -2014,7 +2716,7 @@ begin_transaction (void)
qs = TALER_ARL_edb->select_reserve_closed_above_serial_id (
TALER_ARL_edb->cls,
- pp.last_reserve_close_uuid,
+ TALER_ARL_USE_PP (wire_reserve_close_id),
&reserve_closed_cb,
NULL);
if (0 > qs)
@@ -2116,11 +2818,15 @@ 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 ()));
@@ -2138,7 +2844,7 @@ run (void *cls,
&total_bad_amount_in_minus));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
- &total_missattribution_in));
+ &total_misattribution_in));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (TALER_ARL_currency,
&total_amount_lag));
@@ -2192,11 +2898,14 @@ main (int argc,
"internal",
"perform checks only applicable for exchange-internal audits",
&internal_checks),
- 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',
+ "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
diff --git a/src/auditor/test-auditor.sh b/src/auditor/test-auditor.sh
index 6138b3d35..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,11 +26,11 @@
#
# Requires 'jq' tool and Postgres superuser rights!
set -eu
-set -x
+#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
@@ -28,60 +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
+
-# Exit, with error message (hard failure)
-function exit_fail() {
- echo $1
- exit 1
+# 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
}
# Cleanup to run whenever we exit
-function cleanup()
+function exit_cleanup()
{
- for n in `jobs -p`
+ 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
+ kill "$n" 2> /dev/null || true
done
- wait
+ wait || true
+ echo "DONE"
}
# Install cleanup handler (except for kill -9)
-trap cleanup EXIT
+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 2>bank.err >bank.log &
- for n in `seq 1 80`
+ 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 -y -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
}
@@ -92,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 -i -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 -i -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 -i -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 -i -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"
@@ -120,16 +251,11 @@ function audit_only () {
# Cleanup to run after the auditor
function post_audit () {
- taler-exchange-dbinit -c $CONF -g || exit_fail "exchange DB GC failed"
-
+ 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"
}
@@ -138,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
}
@@ -283,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"
+
+ echo -n "Check for lag detection... "
- LAG=`jq -r .total_amount_lag < test-audit-wire.json`
- if test $LAG = "TESTKUDOS:0"
+ # 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 -At $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 $ROW is 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"
}
@@ -406,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"
}
@@ -469,48 +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_ID=`echo "SELECT w.wire_target_serial_id FROM deposits d, wire_targets w WHERE d.wire_target_serial_id = w.wire_target_serial_id AND deposit_serial_id=${SERIAL};" | psql $DB -Aqt`
-NEW_WIRE_ID=`echo "INSERT INTO wire_targets (wire_target_serial_id, payto_uri, h_payto, kyc_ok) VALUES (DEFAULT, 'payto://x-taler-bank/localhost/testuser-xxlargtp', '\xe67a8933f7521572236301d7ff63e217245f777ed4b4d7e8df1b989900743658f4c10042ff9e5ce517bff4e5387b27877780673d85393f289aea5ee1946021c3', false) RETURNING wire_target_serial_id;" | psql $DB -Aqt`
-echo OLD_WIRE_ID=$OLD_WIRE_ID
-echo NEW_WIRE_ID=$NEW_WIRE_ID
-echo "UPDATE deposits SET wire_target_serial_id=$NEW_WIRE_ID 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_target_serial_id=$OLD_WIRE_ID 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"
}
@@ -519,42 +740,44 @@ echo "UPDATE deposits SET wire_target_serial_id=$OLD_WIRE_ID WHERE deposit_seria
# 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"
}
@@ -562,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='\x0000000100000000287369672d76616c200a2028727361200a2020287320233542383731423743393036444643303442424430453039353246413642464132463537303139374131313437353746324632323332394644443146324643333445393939413336363430334233413133324444464239413833353833464536354442374335434445304441453035374438363336434541423834463843323843344446304144363030343430413038353435363039373833434431333239393736423642433437313041324632414132414435413833303432434346314139464635394244434346374436323238344143354544364131373739463430353032323241373838423837363535453434423145443831364244353638303232413123290a2020290a20290b' 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"
}
@@ -603,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"
}
@@ -655,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`
+ echo "===========12: incomplete refresh ==========="
+ OLD_ACC=$(echo "DELETE FROM exchange.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 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_ID=`echo "SELECT old_known_coin_id FROM refresh_commitments LIMIT 1;" | psql $DB -Aqt`
-OLD_SIG=`echo "SELECT old_coin_sig FROM refresh_commitments WHERE old_known_coin_id='$COIN_ID';" | psql $DB -Aqt`
-NEW_SIG="\xba588af7c13c477dca1ac458f65cc484db8fba53b969b873f4353ecbd815e6b4c03f42c0cb63a2b609c2d726e612fd8e0c084906a41f409b6a23a08a83c89a02"
-echo "UPDATE refresh_commitments SET old_coin_sig='$NEW_SIG' WHERE old_known_coin_id='$COIN_ID'" | 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================="
-
-# Check wire transfer lag reported (no aggregator!)
+ echo "===========15: deposit wire salt wrong================="
-# 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
+ # 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"
- # 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
-
}
@@ -1131,64 +1342,67 @@ fi
# Test where we trigger an emergency.
function test_18() {
-echo "===========18: emergency================="
+ echo "===========18: emergency================="
-echo "DELETE FROM reserves_out;" | psql -Aqt $DB
+ echo "DELETE FROM exchange.reserves_out;" \
+ | psql -Aqt "$DB" -q
-run_audit
-
-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 ================="
-
-# 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 "===========19: reserve closure done properly ================="
- 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_UUID=`echo "SELECT reserve_uuid FROM reserves_in WHERE reserve_in_serial_id=1;" | psql $DB -Aqt`
- OLD_EXP=`echo "SELECT expiration_date FROM reserves WHERE reserve_uuid='${RES_UUID}';" | 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_uuid='${RES_UUID}';" | 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
@@ -1206,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_UUID=`echo "SELECT reserve_uuid 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_uuid='${RES_UUID}';" | 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_uuid='${RES_UUID}';" | 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_UUID=`echo "SELECT reserve_uuid FROM reserves_in WHERE reserve_in_serial_id=1;" | psql $DB -Aqt`
- OLD_EXP=`echo "SELECT expiration_date FROM reserves WHERE reserve_uuid='${RES_UUID}';" | 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_uuid='${RES_UUID}';" | 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
@@ -1297,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 ================="
-S_DENOM=`echo 'SELECT denominations_serial 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 denominations WHERE denominations_serial='${S_DENOM}';" | psql $DB -Aqt`
-OLD_WEXP=`echo "SELECT expire_withdraw FROM denominations WHERE denominations_serial='${S_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 denominations SET expire_withdraw=${NEW_WEXP} WHERE denominations_serial='${S_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 denominations SET expire_withdraw=${OLD_WEXP} WHERE denominations_serial='${S_DENOM}';" | psql -Aqt $DB
+ # Undo modification
+ echo "UPDATE exchange.denominations SET expire_withdraw=${OLD_WEXP} WHERE denominations_serial='${S_DENOM}';" | psql -Aqt "$DB"
}
@@ -1333,50 +1545,46 @@ echo "UPDATE denominations SET expire_withdraw=${OLD_WEXP} WHERE denominations_s
# 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
@@ -1386,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
}
@@ -1416,179 +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_ID=`echo "SELECT w.wire_target_serial_id FROM deposits d, wire_targets w WHERE d.wire_target_serial_id = w.wire_target_serial_id AND deposit_serial_id=${SERIAL};" | psql $DB -Aqt`
-NEW_WIRE_ID=`echo "INSERT INTO wire_targets (wire_target_serial_id, payto_uri, h_payto, kyc_ok) VALUES (DEFAULT, 'payto://x-taler-bank/localhost/testuser-xxlargtp', '\xe67a8933f7521572236301d7ff63e217245f777ed4b4d7e8df1b989900743658f4c10042ff9e5ce517bff4e5387b27877780673d85393f289aea5ee1946021c3', false) RETURNING wire_target_serial_id;" | psql $DB -Aqt`
-echo OLD_WIRE_ID=$OLD_WIRE_ID
-echo NEW_WIRE_ID=$NEW_WIRE_ID
-echo "UPDATE deposits SET wire_target_serial_id=$NEW_WIRE_ID WHERE deposit_serial_id=${SERIAL}" | psql -Aqt $DB
-
-run_audit
+ 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"
-echo -n "Testing inconsistency detection... "
+ run_audit
-jq -e .bad_sig_losses[0] < test-audit-coins.json > /dev/null || exit_fail "Bad signature not detected"
+ echo -n "Testing inconsistency 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
+ jq -e .bad_sig_losses[0] < test-audit-coins.json > /dev/null || exit_fail "Bad signature not detected"
-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_target_serial_id=$OLD_WIRE_ID 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`
- echo WTID=$WTID
- 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
-
}
@@ -1597,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='\x0000000100000000287369672d76616c200a2028727361200a2020287320233542383731423743393036444643303442424430453039353246413642464132463537303139374131313437353746324632323332394644443146324643333445393939413336363430334233413133324444464239413833353833464536354442374335434445304441453035374438363336434541423834463843323843344446304144363030343430413038353435363039373833434431333239393736423642433437313041324632414132414435413833303432434346314139464635394244434346374436323238344143354544364131373739463430353032323241373838423837363535453434423145443831364244353638303232413123290a2020290a20290b' 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
@@ -1645,10 +1833,6 @@ then
echo "OK"
# cannot easily undo aggregator, hence full reload
full_reload
-
-else
- echo "Test skipped (database too new)"
-fi
}
@@ -1656,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 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_minus < 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 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"
}
@@ -1684,28 +1868,28 @@ echo "UPDATE denominations SET fee_withdraw_frac=2000000 WHERE coin_val=1;" | ps
# 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 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 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"
}
@@ -1714,39 +1898,27 @@ echo "UPDATE denominations SET fee_refresh_frac=3000000 WHERE coin_val=1;" | psq
# 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 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 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"
}
@@ -1756,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='\x0000000100000000287369672d76616c200a2028727361200a2020287320233542383731423743393036444643303442424430453039353246413642464132463537303139374131313437353746324632323332394644443146324643333445393939413336363430334233413133324444464239413833353833464536354442374335434445304441453035374438363336434541423834463843323843344446304144363030343430413038353435363039373833434431333239393736423642433437313041324632414132414435413833303432434346314139464635394244434346374436323238344143354544364131373739463430353032323241373838423837363535453434423145443831364244353638303232413123290a2020290a20290b' 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
@@ -1787,32 +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`
-
- # 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
@@ -1821,7 +2122,7 @@ check_with_database()
fail=0
for i in $TESTS
do
- test_$i
+ "test_$i"
if test 0 != $fail
then
break
@@ -1834,60 +2135,121 @@ check_with_database()
-
-
# *************** 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 --help >/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
-# run tests with pre-build database, if one is available
-if test -r auditor-basedb.mpub
+check_with_database "$MYDIR/$DB"
+if [ "$fail" != "0" ]
then
- check_with_database "auditor-basedb"
-else
- echo "Lacking auditor-basedb.mpub, skipping test"
- fail=77
+ 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 766825723..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,62 +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
-# Exit, with error message (hard failure)
-function exit_fail() {
- echo $1
- exit 1
+# 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
}
# Cleanup to run whenever we exit
-function cleanup()
+function exit_cleanup()
{
- for n in `jobs -p`
+ 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
+ kill "$n" 2> /dev/null || true
done
wait
+ echo "DONE"
}
# Install cleanup handler (except for kill -9)
-trap cleanup EXIT
+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 2>bank.err >bank.log &
- for n in `seq 1 80`
+ 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
+ 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 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
}
@@ -89,47 +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 -i -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 -i -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 -i -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 -i -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 () {
- echo -n "Cleanup ..."
cleanup
- 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"
-
+ 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"
}
@@ -139,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
-
-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 "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"
-# cannot easily undo aggregator, hence full reload
-full_reload
+ 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
}
@@ -283,58 +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 "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 "===========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 "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"
}
@@ -342,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"
}
@@ -381,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"
}
@@ -409,72 +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")
- # 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 -f test-audit.log test-wire-audit.log
+ dropdb "$DB"
}
@@ -482,54 +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 --help >/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
-fi
-
-# run tests with pre-build database, if one is available
-if test -r revoke-basedb.mpub
-then
- check_with_database "revoke-basedb"
else
- fail=77
+ echo "Generation failed"
fi
-exit $fail
+exit 0
diff --git a/src/auditor/test-sync.sh b/src/auditor/test-sync.sh
index 24de92ebe..bcef908aa 100755
--- a/src/auditor/test-sync.sh
+++ b/src/auditor/test-sync.sh
@@ -1,42 +1,167 @@
-#!/bin/sh
+#!/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
-echo -n "Testing synchronization logic ..."
+# Exit, with status code "skip" (no 'real' failure)
+function exit_skip() {
+ echo "SKIPPING test: $1"
+ exit 77
+}
-dropdb talercheck-in 2> /dev/null || true
-dropdb talercheck-out 2> /dev/null || true
+# Exit, with error message (hard failure)
+function exit_fail() {
+ echo "FAILING test: $1"
+ exit 1
+}
-createdb talercheck-in || exit 77
-createdb talercheck-out || exit 77
-echo -n "."
+# 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
+}
-taler-exchange-dbinit -c test-sync-out.conf
-echo -n "."
-psql talercheck-in < auditor-basedb.sql >/dev/null 2> /dev/null
+# Install cleanup handler (except for kill -9)
+trap cleanup EXIT
-echo -n "."
-taler-auditor-sync -s test-sync-in.conf -d test-sync-out.conf -t
+function check_with_database()
+{
+ echo -n "Testing synchronization logic ..."
-for table in denominations denomination_revocations reserves reserves_in reserves_close reserves_out auditors auditor_denom_sigs exchange_sign_keys signkey_revocations known_coins refresh_commitments refresh_revealed_coins refresh_transfer_keys deposits refunds wire_out aggregation_tracking wire_fee recoup recoup_refresh
-do
+ 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 "."
- CIN=`echo "SELECT COUNT(*) FROM $table" | psql talercheck-in -Aqt`
- COUT=`echo "SELECT COUNT(*) FROM $table" | psql talercheck-out -Aqt`
- if test ${CIN} != ${COUT}
- then
- dropdb talercheck-in
- dropdb talercheck-out
- echo "FAIL"
- echo "Record count mismatch: $CIN / $COUT in table $table"
- exit 1
- fi
-done
+ 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
-echo -n ". "
-dropdb talercheck-in
-dropdb talercheck-out
+ # 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)
-echo "PASS"
+ 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 92aabcd31..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,17 +57,48 @@ 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 \
- -lpthread \
+ -lpq \
$(XLIB)
lib_LTLIBRARIES = \
@@ -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
diff --git a/src/auditordb/auditor-0001.sql b/src/auditordb/auditor-0001.sql
index 0faa890d7..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,246 +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
+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_exchanges
- IS 'list of the exchanges we are auditing';
-
-
-CREATE TABLE IF NOT EXISTS auditor_exchange_signkeys
- (master_pub BYTEA NOT NULL 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)
+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_exchange_signkeys
- IS 'list of the online signing keys of exchanges we are auditing';
-
-
-CREATE TABLE IF NOT EXISTS auditor_progress_reserve
- (master_pub BYTEA NOT NULL 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 NOT NULL 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 NOT NULL 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 NOT NULL 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 NOT NULL
- ,wire_out_off INT8 NOT NULL
- ,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 NOT NULL 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 NOT NULL 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 NOT NULL 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 NOT NULL 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 CHECK (LENGTH(denom_pub_hash)=64)
- ,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 NOT NULL 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
- );
-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 NOT NULL 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 NOT NULL 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
- );
-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 NOT NULL CONSTRAINT master_pub_ref REFERENCES auditor_exchanges(master_pub) ON DELETE CASCADE
- ,serial_id BIGSERIAL NOT NULL UNIQUE
- ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
- ,h_extensions BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=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
- ,amount_without_fee_val INT8 NOT NULL
- ,amount_without_fee_frac INT4 NOT NULL
- ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)
- ,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)
- ,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 NOT NULL 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 8e42c8094..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 (auditor-0001.sql)
-SELECT _v.unregister_patch('auditor-0001');
-
--- 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 7931900a5..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-2021 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,60 +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)
-
-
-/**
- * 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;
-
- /**
- * 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.
@@ -97,20 +75,23 @@ struct PostgresClosure
*/
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;
}
@@ -118,23 +99,129 @@ 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 enum GNUNET_GenericReturnValue
-postgres_create_tables (void *cls)
+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);
+}
+
+
+/**
+ * Stop notifications.
+ *
+ * @param eh handle to unregister.
+ */
+static void
+postgres_event_listen_cancel (struct GNUNET_DB_EventHandler *eh)
+{
+ GNUNET_PQ_event_listen_cancel (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
+ */
+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);
}
@@ -144,510 +231,12 @@ postgres_create_tables (void *cls)
* @param[in,out] pg the plugin-specific state
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
setup_connection (struct PostgresClosure *pg)
{
- 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_deposit_confirmation() */
- GNUNET_PQ_make_prepare ("auditor_deposit_confirmation_insert",
- "INSERT INTO deposit_confirmations "
- "(master_pub"
- ",h_contract_terms"
- ",h_extensions"
- ",h_wire"
- ",exchange_timestamp"
- ",wire_deadline"
- ",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,$13,$14);",
- 14),
- /* Used in #postgres_get_deposit_confirmations() */
- GNUNET_PQ_make_prepare ("auditor_deposit_confirmation_select",
- "SELECT"
- " serial_id"
- ",h_contract_terms"
- ",h_extensions"
- ",h_wire"
- ",exchange_timestamp"
- ",wire_deadline"
- ",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;
@@ -659,11 +248,12 @@ setup_connection (struct PostgresClosure *pg)
db_conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
"auditordb-postgres",
NULL,
- NULL,
- ps);
+ es,
+ NULL);
if (NULL == db_conn)
return GNUNET_SYSERR;
pg->conn = db_conn;
+ pg->prep_gen++;
return GNUNET_OK;
}
@@ -778,6 +368,9 @@ postgres_commit (void *cls)
GNUNET_PQ_query_param_end
};
+ PREPARE (pg,
+ "do_commit",
+ "COMMIT");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"do_commit",
params);
@@ -796,7 +389,7 @@ static enum GNUNET_GenericReturnValue
postgres_gc (void *cls)
{
struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Absolute now = {0};
struct GNUNET_PQ_QueryParam params_time[] = {
GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_end
@@ -804,19 +397,18 @@ postgres_gc (void *cls)
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 (pg->cfg,
"auditordb-postgres",
NULL,
- NULL,
+ es,
ps);
if (NULL == conn)
return GNUNET_SYSERR;
@@ -836,1993 +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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const char *exchange_url)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 master_pub master public key of the exchange
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_delete_exchange (void *cls,
- const struct TALER_MasterPublicKeyP *master_pub)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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;
-
- 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 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,
- TALER_AUDITORDB_ExchangeCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = 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;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->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 sk signing key information to store
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_exchange_signkey (
- void *cls,
- const struct TALER_AUDITORDB_ExchangeSigningKey *sk)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&sk->master_public_key),
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 dc deposit confirmation information to store
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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->master_public_key),
- GNUNET_PQ_query_param_auto_from_type (&dc->h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (&dc->h_extensions),
- 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 (&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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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_extensions",
- &dc.h_extensions),
- 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 ("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;
- if (GNUNET_OK !=
- dcc->cb (dcc->cb_cls,
- serial_id,
- &dc))
- break;
- }
-}
-
-
-/**
- * Get information about deposit confirmations from the database.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @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,
- 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 (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;
-}
-
-
-/**
- * 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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointReserve *ppr)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointReserve *ppr)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_ProgressPointReserve *ppr)
-{
- 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[] = {
- 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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointAggregation *ppa)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointAggregation *ppa)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_ProgressPointAggregation *ppa)
-{
- 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[] = {
- GNUNET_PQ_result_spec_uint64 ("last_wire_out_serial_id",
- &ppa->last_wire_out_serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointDepositConfirmation *ppdc)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointDepositConfirmation *ppdc)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_ProgressPointDepositConfirmation *ppdc)
-{
- 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[] = {
- GNUNET_PQ_result_spec_uint64 ("last_deposit_confirmation_serial_id",
- &ppdc->last_deposit_confirmation_serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointCoin *ppc)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointCoin *ppc)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_ProgressPointCoin *ppc)
-{
- 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[] = {
- 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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->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 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,
- 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 PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- 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 PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- 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 PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_WireProgressPoint *pp)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (master_pub),
- GNUNET_PQ_query_param_timestamp (&pp->last_timestamp),
- GNUNET_PQ_query_param_uint64 (&pp->last_reserve_close_uuid),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_WireProgressPoint *pp)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_timestamp (&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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_WireProgressPoint *pp)
-{
- 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[] = {
- GNUNET_PQ_result_spec_timestamp ("last_timestamp",
- &pp->last_timestamp),
- GNUNET_PQ_result_spec_uint64 ("last_reserve_close_uuid",
- &pp->last_reserve_close_uuid),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->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 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,
- 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_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),
- 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_timestamp (&expiration_date),
- GNUNET_PQ_query_param_string (origin_account),
- GNUNET_PQ_query_param_end
- };
-
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (reserve_balance,
- withdraw_fee_balance));
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- 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_Timestamp expiration_date)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_amount (reserve_balance),
- TALER_PQ_query_param_amount (withdraw_fee_balance),
- GNUNET_PQ_query_param_timestamp (&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
- };
-
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (reserve_balance,
- withdraw_fee_balance));
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "auditor_reserves_update",
- params);
-}
-
-
-/**
- * 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
- * @param master_pub master public key of the exchange
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_del_reserve_info (void *cls,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_MasterPublicKeyP *master_pub)
-{
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "auditor_reserves_delete",
- params);
-}
-
-
-/**
- * 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 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,
- 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_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_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_timestamp ("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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *reserve_balance,
- const struct TALER_Amount *withdraw_fee_balance)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (reserve_balance,
- withdraw_fee_balance));
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *reserve_balance,
- const struct TALER_Amount *withdraw_fee_balance)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- 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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *wire_fee_balance)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *wire_fee_balance)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- 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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->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 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,
- const struct TALER_DenominationHash *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 PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_DenominationHash *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 PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct
- TALER_DenominationHash *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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->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 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,
- 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 PostgresClosure *pg = cls;
- 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
- };
-
- 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 (pg->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 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,
- 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 PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- 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 (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_DenominationHash *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 (master_pub),
- GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
- GNUNET_PQ_query_param_timestamp (&revenue_timestamp),
- TALER_PQ_query_param_amount (revenue_balance),
- TALER_PQ_query_param_amount (loss_balance),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 TALER_DenominationHash 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;
- }
-}
-
-
-/**
- * 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 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,
- 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 (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- 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_auto_from_type (master_pub),
- GNUNET_PQ_query_param_timestamp (&start_time),
- GNUNET_PQ_query_param_timestamp (&end_time),
- TALER_PQ_query_param_amount (reserve_profits),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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_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;
- }
-}
-
-
-/**
- * Return information about an exchange's historic revenue from reserves.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @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,
- 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 (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *balance)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_Amount *balance)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- 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 (pg->conn,
- "auditor_predicted_result_select",
- params,
- rs);
-}
-
-
-/**
* Initialize Postgres database subsystem.
*
* @param cls a configuration instance
@@ -2849,83 +454,81 @@ libtaler_plugin_auditordb_postgres_init (void *cls)
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->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;
}
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 f8faa0694..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,15 +53,124 @@ 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;
+
+/**
+ * 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)
+{
+ 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;
+}
+
+
+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_historic_reserve_revenue_result: result does not match\n");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
/**
* Main function that will be run by the scheduler.
@@ -88,7 +196,9 @@ 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;
@@ -128,24 +238,17 @@ run (void *cls)
TALER_string_to_amount (CURRENCY ":0.000014",
&fee_refund));
- struct TALER_MasterPublicKeyP master_pub;
struct TALER_ReservePublicKeyP reserve_pub;
- struct TALER_DenominationHash rnd_hash;
struct TALER_DenominationPrivateKey denom_priv;
struct TALER_DenominationPublicKey denom_pub;
- struct TALER_DenominationHash denom_pub_hash;
- struct GNUNET_TIME_Timestamp now;
- struct GNUNET_TIME_Timestamp past;
- struct GNUNET_TIME_Timestamp future;
struct GNUNET_TIME_Timestamp date;
- RND_BLK (&master_pub);
RND_BLK (&reserve_pub);
RND_BLK (&rnd_hash);
GNUNET_assert (GNUNET_OK ==
TALER_denom_priv_create (&denom_priv,
&denom_pub,
- TALER_DENOMINATION_RSA,
+ GNUNET_CRYPTO_BSA_RSA,
1024));
TALER_denom_pub_hash (&denom_pub,
&denom_pub_hash);
@@ -164,487 +267,182 @@ run (void *cls)
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,
- &master_pub,
- "https://exchange/"));
-
-
- 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,
- &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,
- &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,
- &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));
- 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,
- &reserve_pub,
- &master_pub,
- &reserve_balance,
- &withdraw_fee_balance,
- 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,
- &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,
- &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,
- &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,
- &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,
- &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,
- &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,
- &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,
- &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,
- &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,
- &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,
- &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,
- &master_pub,
&denom_pub_hash,
past,
&rbalance,
- &loss));
-
+ &rloss));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->insert_historic_denom_revenue (plugin->cls,
- &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 TALER_DenominationHash *denom_pub_hash2,
- struct GNUNET_TIME_Timestamp 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,
- &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,
- &master_pub,
past,
future,
&reserve_profits));
-
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->insert_historic_reserve_revenue (plugin->cls,
- &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_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 != 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,
- &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,
- &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,
- &master_pub,
- &rbalance));
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_wire_fee_summary (plugin->cls,
- &master_pub,
- &rbalance));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->update_wire_fee_summary (plugin->cls,
- &master_pub,
- &reserve_profits));
- {
- struct TALER_Amount rprof;
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_wire_fee_summary (plugin->cls,
- &master_pub,
- &rprof));
- FAILIF (0 !=
- TALER_amount_cmp (&rprof,
- &reserve_profits));
- }
FAILIF (0 >
plugin->commit (plugin->cls));
-
- FAILIF (GNUNET_OK !=
- plugin->start (plugin->cls));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: get_predicted_balance\n");
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_predicted_balance (plugin->cls,
- &master_pub,
- &rbalance2));
-
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->del_reserve_info (plugin->cls,
- &reserve_pub,
- &master_pub));
-
- FAILIF (0 != TALER_amount_cmp (&rbalance2,
- &rbalance));
-
- plugin->rollback (plugin->cls);
+ &reserve_pub));
#if GC_IMPLEMENTED
FAILIF (GNUNET_OK !=
@@ -654,18 +452,7 @@ run (void *cls)
result = 0;
drop:
- {
- plugin->rollback (plugin->cls);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Test: auditor_delete_exchange\n");
- GNUNET_break (GNUNET_OK ==
- plugin->start (plugin->cls));
- GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
- plugin->delete_exchange (plugin->cls,
- &master_pub));
- GNUNET_break (0 <=
- plugin->commit (plugin->cls));
- }
+ plugin->rollback (plugin->cls);
GNUNET_break (GNUNET_OK ==
plugin->drop_tables (plugin->cls,
GNUNET_YES));
@@ -686,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 ();
@@ -709,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 ffd428a6f..a292dcece 100644
--- a/src/bank-lib/Makefile.am
+++ b/src/bank-lib/Makefile.am
@@ -51,14 +51,42 @@ libtalerbank_la_LIBADD = \
-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 \
@@ -66,7 +94,7 @@ libtalerfakebank_la_LIBADD = \
-lgnunetjson \
-lgnunetutil \
-ljansson \
- $(LIBGNURLCURL_LIBS) \
+ -lcurl \
-lmicrohttpd \
-lpthread \
$(XLIB)
diff --git a/src/bank-lib/bank_api_admin.c b/src/bank-lib/bank_api_admin.c
index 77b1a38e4..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_Timestamp 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_TS;
switch (response_code)
{
case 0:
- ec = TALER_EC_GENERIC_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),
+ &ir.details.ok.serial_id),
GNUNET_JSON_spec_timestamp ("timestamp",
- &timestamp),
+ &ir.details.ok.timestamp),
GNUNET_JSON_spec_end ()
};
@@ -102,42 +102,41 @@ handle_admin_add_incoming_finished (void *cls,
NULL, NULL))
{
GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_GENERIC_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? */
- ec = TALER_JSON_get_error_code (j);
+ 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 */
@@ -145,15 +144,11 @@ 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);
+ 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);
}
@@ -215,9 +210,10 @@ TALER_BANK_admin_add_incoming (
"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) ||
diff --git a/src/bank-lib/bank_api_common.c b/src/bank-lib/bank_api_common.c
index f749e0a92..2c47429ad 100644
--- a/src/bank-lib/bank_api_common.c
+++ b/src/bank-lib/bank_api_common.c
@@ -23,11 +23,11 @@
#include "bank_api_common.h"
-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 1ea2e269e..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--2021 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
@@ -77,62 +77,67 @@ 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")))
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (history,
+ spec,
+ NULL,
+ NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if (! json_is_array (history_array))
{
- 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_any ("amount",
- &td.amount),
- GNUNET_JSON_spec_timestamp ("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_uri),
- GNUNET_JSON_spec_string ("credit_account",
- &td.credit_account_uri),
- 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);
- 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 (size_t i = 0; i<len; i++)
{
- 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;
}
@@ -152,69 +157,67 @@ 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_GENERIC_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_GENERIC_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;
- break;
+ TALER_BANK_credit_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);
+ 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);
- ec = TALER_JSON_get_error_code (j);
+ 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);
}
@@ -248,12 +251,15 @@ TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx,
{
if ( (0 < num_results) &&
(! GNUNET_TIME_relative_is_zero (timeout)) )
+ /* 0 == start_row is implied, go with timeout into future */
GNUNET_snprintf (url,
sizeof (url),
"history/incoming?delta=%lld&long_poll_ms=%llu",
(long long) num_results,
tms);
else
+ /* Going back from current transaction or have no timeout;
+ hence timeout makes no sense */
GNUNET_snprintf (url,
sizeof (url),
"history/incoming?delta=%lld",
@@ -263,6 +269,7 @@ TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx,
{
if ( (0 < num_results) &&
(! GNUNET_TIME_relative_is_zero (timeout)) )
+ /* going forward from num_result */
GNUNET_snprintf (url,
sizeof (url),
"history/incoming?delta=%lld&start=%llu&long_poll_ms=%llu",
@@ -270,6 +277,8 @@ TALER_BANK_credit_history (struct GNUNET_CURL_Context *ctx,
(unsigned long long) start_row,
tms);
else
+ /* going backwards or have no timeout;
+ hence timeout makes no sense */
GNUNET_snprintf (url,
sizeof (url),
"history/incoming?delta=%lld&start=%llu",
diff --git a/src/bank-lib/bank_api_debit.c b/src/bank-lib/bank_api_debit.c
index 2a76495bd..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--2021 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
@@ -77,64 +77,69 @@ 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")))
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (history,
+ spec,
+ NULL,
+ NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if (! json_is_array (history_array))
{
- 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_any ("amount",
- &td.amount),
- GNUNET_JSON_spec_timestamp ("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_uri),
- GNUNET_JSON_spec_string ("debit_account",
- &td.debit_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);
+ 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;
}
@@ -154,69 +159,64 @@ 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_GENERIC_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_GENERIC_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);
- ec = TALER_JSON_get_error_code (j);
+ 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);
}
diff --git a/src/bank-lib/bank_api_transfer.c b/src/bank-lib/bank_api_transfer.c
index bbfc9cec8..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,
@@ -110,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;
}
@@ -169,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_Timestamp timestamp = GNUNET_TIME_UNIT_FOREVER_TS;
- 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_GENERIC_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),
+ &tr.details.ok.row_id),
GNUNET_JSON_spec_timestamp ("timestamp",
- &timestamp),
+ &tr.details.ok.timestamp),
GNUNET_JSON_spec_end ()
};
@@ -195,39 +185,38 @@ handle_transfer_finished (void *cls,
NULL, NULL))
{
GNUNET_break_op (0);
- response_code = 0;
- ec = TALER_EC_GENERIC_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 */
@@ -235,29 +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);
+ 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,
@@ -366,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 2ccc2c751..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-2021 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
@@ -21,1261 +21,20 @@
* @brief library that fakes being a Taler bank for testcases
* @author Christian Grothoff <christian@grothoff.org>
*/
-// TODO: support adding WAD transfers
-
#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>
-
-/**
- * Maximum POST request size (for /admin/add-incoming)
- */
-#define REQUEST_BUFFER_MAX (4 * 1024)
-
-/**
- * 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
-
-/**
- * Per account information.
- */
-struct Account;
-
-
-/**
- * Types of long polling activities.
- */
-enum LongPollType
-{
- /**
- * Transfer TO the exchange.
- */
- LP_CREDIT,
-
- /**
- * Transfer FROM the exchange.
- */
- LP_DEBIT
-
-};
-
-/**
- * Client waiting for activity on this account.
- */
-struct LongPoller
-{
-
- /**
- * Kept in a DLL.
- */
- struct LongPoller *next;
-
- /**
- * Kept in a DLL.
- */
- struct LongPoller *prev;
-
- /**
- * Account this long poller is waiting on.
- */
- struct Account *account;
-
- /**
- * 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;
-
-/**
- * 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;
-
- /**
- * 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;
-};
-
-
-/**
- * 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 re-use.
- */
- struct GNUNET_CONTAINER_MultiPeerMap *rpubs;
-
- /**
- * 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;
-
- /**
- * 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;
-
- /**
- * BaseURL of the fakebank.
- */
- char *my_baseurl;
-
- /**
- * Our port number.
- */
- uint16_t port;
-
- /**
- * Event FD to signal @a lp_thread a change in
- * @a lp_heap.
- */
- int lp_event;
-
- /**
- * 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
-};
-
-
-/**
- * Special address "con_cls" can point to to indicate that the handler has
- * been called more than once already (was previously suspended).
- */
-static int special_ptr;
-
-
-/**
- * Task run whenever HTTP server operations are pending.
- *
- * @param cls the `struct TALER_FAKEBANK_Handle`
- */
-static void
-run_mhd (void *cls);
-
-
-/**
- * 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
- * @param[in,out] h fakebank handle
- */
-static void
-lp_trigger (struct LongPoller *lp,
- struct TALER_FAKEBANK_Handle *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;
- if (-1 != h->lp_event)
- {
- if (NULL != h->mhd_task)
- GNUNET_SCHEDULER_cancel (h->mhd_task);
- h->mhd_task =
- GNUNET_SCHEDULER_add_now (&run_mhd,
- h);
- }
-}
-
-
-/**
- * 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
- */
-static void *
-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));
- lp_trigger (lp,
- h);
- 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 = {
- .fd = h->lp_event,
- .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;
-
- iret = read (h->lp_event,
- &ev,
- sizeof (ev));
- 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;
-}
-
-
-/**
- * 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
- * @return account handle (never NULL)
- */
-static struct Account *
-lookup_account (struct TALER_FAKEBANK_Handle *h,
- const char *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)
- {
- account = GNUNET_new (struct Account);
- account->account_name = GNUNET_strdup (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;
-}
-
-
-/**
- * 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 = lookup_account (h,
- want_debit);
- credit_account = lookup_account (h,
- want_credit);
- 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 = lookup_account (h,
- want_debit);
- credit_account = lookup_account (h,
- want_credit);
- 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;
-}
-
-
-/**
- * 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
- */
-static void
-post_transaction (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);
-}
-
-
-/**
- * Trigger long pollers that might have been waiting
- * for @a t.
- *
- * @param h fakebank handle
- * @param t transaction to notify on
- */
-static void
-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));
- lp_trigger (lp,
- h);
- }
- }
- 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));
- lp_trigger (lp,
- h);
- }
- }
- GNUNET_assert (0 ==
- pthread_mutex_unlock (&h->big_lock));
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_GenericReturnValue
-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 = lookup_account (h,
- debit_account);
- credit_acc = lookup_account (h,
- 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;
- 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;
- post_transaction (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;
- notify_transaction (h,
- t);
- 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
- * @param[out] row_id serial_id of the transfer
- * @param[out] timestamp when was the transfer made
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-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 = lookup_account (h,
- debit_account);
- credit_acc = lookup_account (h,
- 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;
- post_transaction (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);
- notify_transaction (h,
- t);
- return GNUNET_OK;
-}
-
-
-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;
-}
-
-
-static int
-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);
- 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
- if (-1 != h->lp_event)
- {
- 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)))
- lp_trigger (lp,
- h);
- GNUNET_break (sizeof (val) ==
- write (h->lp_event,
- &val,
- sizeof (val)));
- GNUNET_assert (0 ==
- pthread_mutex_unlock (&h->big_lock));
- GNUNET_break (0 ==
- pthread_join (h->lp_thread,
- &ret));
- GNUNET_break (NULL == ret);
- GNUNET_break (0 == close (h->lp_event));
- h->lp_event = -1;
- }
- else
- {
- struct LongPoller *lp;
-
- while (NULL != (lp = GNUNET_CONTAINER_heap_remove_root (h->lp_heap)))
- lp_trigger (lp,
- h);
- }
- 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);
- }
- 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);
-}
+#include "fakebank.h"
+#include "fakebank_bank.h"
+#include "fakebank_common_lp.h"
+#include "fakebank_tbi.h"
/**
@@ -1284,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
@@ -1298,1053 +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;
- if (NULL == *con_cls)
- return;
- if (&special_ptr == *con_cls)
- return;
- 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 MHD_RESULT
-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;
- struct GNUNET_TIME_Timestamp timestamp;
-
- 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;
- 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);
- 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 = 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));
-}
-
-
-/**
- * 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 MHD_RESULT
-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;
- struct GNUNET_TIME_Timestamp ts;
-
- 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;
- 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;
- }
- {
- int ret;
-
- credit = TALER_xtalerbank_account_from_payto (credit_account);
- ret = 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));
-}
-
-
-/**
- * Handle incoming HTTP request for / (home page).
- *
- * @param h the fakebank handle
- * @param connection the connection
- * @return MHD result code
- */
-static MHD_RESULT
-handle_home_page (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;
-}
-
-
-/**
- * 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;
-
- /**
- * true if starting point was given.
- */
- bool have_start;
-
-};
-
-
-/**
- * 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
- */
-static enum GNUNET_GenericReturnValue
-parse_history_common_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 : h->serial_counter;
- 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;
-}
-
-
-/**
- * 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;
+ struct ConnectionContext *cc = *con_cls;
- 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);
- lp_trigger (lp,
- h);
- }
- if (NULL == lp)
+ (void) h;
+ (void) connection;
+ (void) toe;
+ if (NULL == cc)
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));
- if (-1 != h->lp_event)
- {
- uint64_t num = 1;
-
- GNUNET_break (sizeof (num) ==
- write (h->lp_event,
- &num,
- sizeof (num)));
- }
- else
- {
- if (NULL != h->lp_task)
- GNUNET_SCHEDULER_cancel (h->lp_task);
- h->lp_task = GNUNET_SCHEDULER_add_at (t,
- &lp_timeout,
- h);
- }
-}
-
-
-/**
- * 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
- */
-static void
-start_lp (struct TALER_FAKEBANK_Handle *h,
- struct MHD_Connection *connection,
- struct Account *acc,
- struct GNUNET_TIME_Relative lp_timeout,
- enum LongPollType dir)
-{
- struct LongPoller *lp;
- bool toc;
-
- lp = GNUNET_new (struct LongPoller);
- lp->account = acc;
- 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);
-
-}
-
-
-/**
- * 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 (NULL or &special_ptr)
- */
-static MHD_RESULT
-handle_debit_history (struct TALER_FAKEBANK_Handle *h,
- struct MHD_Connection *connection,
- const char *account,
- void **con_cls)
-{
- struct HistoryArgs ha;
- struct Account *acc;
- struct Transaction *pos;
- json_t *history;
- char *debit_payto;
- enum GNUNET_GenericReturnValue ret;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Handling /history/outgoing connection %p\n",
- connection);
- if (GNUNET_OK !=
- (ret = parse_history_common_args (h,
- connection,
- &ha)))
- {
- GNUNET_break_op (0);
- return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
- }
- if (&special_ptr == *con_cls)
- ha.lp_timeout = GNUNET_TIME_UNIT_ZERO;
- acc = lookup_account (h,
- account);
- GNUNET_asprintf (&debit_payto,
- "payto://x-taler-bank/localhost/%s",
- account);
- history = json_array ();
- if (NULL == history)
- {
- GNUNET_break (0);
- GNUNET_free (debit_payto);
- return MHD_NO;
- }
- GNUNET_assert (0 ==
- pthread_mutex_lock (&h->big_lock));
- if (! ha.have_start)
- {
- pos = (0 > ha.delta)
- ? acc->out_tail
- : acc->out_head;
- }
- else
- {
- struct Transaction *t = h->transactions[ha.start_idx % h->ram_limit];
- bool overflow;
- uint64_t dir;
- bool skip = true;
-
- dir = (0 > ha.delta) ? (h->ram_limit - 1) : 1;
- overflow = (t->row_id != ha.start_idx);
- /* If account does not match, linear scan for
- first matching account. */
- while ( (! overflow) &&
- (NULL != t) &&
- (t->debit_account != acc) )
- {
- skip = false;
- t = h->transactions[(t->row_id + dir) % h->ram_limit];
- if ( (NULL != t) &&
- (t->row_id == ha.start_idx) )
- overflow = true; /* full circle, give up! */
- }
- if ( (NULL == t) ||
- overflow)
- {
- GNUNET_free (debit_payto);
- if (GNUNET_TIME_relative_is_zero (ha.lp_timeout) &&
- (0 < ha.delta))
- {
- GNUNET_assert (0 ==
- pthread_mutex_unlock (&h->big_lock));
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal (
- "outgoing_transactions",
- history));
- }
- *con_cls = &special_ptr;
- start_lp (h,
- connection,
- acc,
- ha.lp_timeout,
- LP_DEBIT);
- GNUNET_assert (0 ==
- pthread_mutex_unlock (&h->big_lock));
- json_decref (history);
- return MHD_YES;
- }
- if (t->debit_account != acc)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid start specified, transaction %llu not with account %s!\n",
- (unsigned long long) ha.start_idx,
- account);
- GNUNET_assert (0 ==
- pthread_mutex_unlock (&h->big_lock));
- GNUNET_free (debit_payto);
- json_decref (history);
- return MHD_NO;
- }
- if (skip)
- {
- /* range is exclusive, skip the matching entry */
- if (0 > 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) ha.delta,
- (unsigned long long) pos->row_id);
- else
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No debit transactions exist after given starting point\n");
- while ( (0 != 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 > ha.delta)
- pos = pos->prev_in;
- if (0 < ha.delta)
- pos = pos->next_in;
- continue;
- }
- GNUNET_asprintf (&credit_payto,
- "payto://x-taler-bank/localhost/%s",
- pos->credit_account->account_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 ("debit_account",
- debit_payto), // FIXME: inefficient to return this here always!
- 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 (history,
- trans));
- if (ha.delta > 0)
- ha.delta--;
- else
- ha.delta++;
- if (0 > ha.delta)
- pos = pos->prev_out;
- if (0 < ha.delta)
- pos = pos->next_out;
- }
- if ( (0 == json_array_size (history)) &&
- (! GNUNET_TIME_relative_is_zero (ha.lp_timeout)) &&
- (0 < ha.delta))
- {
- *con_cls = &special_ptr;
- start_lp (h,
- connection,
- acc,
- ha.lp_timeout,
- LP_DEBIT);
- GNUNET_assert (0 ==
- pthread_mutex_unlock (&h->big_lock));
- json_decref (history);
- return MHD_YES;
- }
- GNUNET_assert (0 ==
- pthread_mutex_unlock (&h->big_lock));
- GNUNET_free (debit_payto);
- return TALER_MHD_REPLY_JSON_PACK (connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal (
- "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
- * @param con_cls closure for request (NULL or &special_ptr)
- * @return MHD result code
- */
-static MHD_RESULT
-handle_credit_history (struct TALER_FAKEBANK_Handle *h,
- struct MHD_Connection *connection,
- const char *account,
- void **con_cls)
-{
- struct HistoryArgs ha;
- struct Account *acc;
- const struct Transaction *pos;
- json_t *history;
- char *credit_payto;
- enum GNUNET_GenericReturnValue ret;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Handling /history/incoming connection %p (%d)\n",
- connection,
- (*con_cls == &special_ptr));
- if (GNUNET_OK !=
- (ret = parse_history_common_args (h,
- connection,
- &ha)))
- {
- GNUNET_break_op (0);
- return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
- }
- if (&special_ptr == *con_cls)
- ha.lp_timeout = GNUNET_TIME_UNIT_ZERO;
- *con_cls = &special_ptr;
- acc = lookup_account (h,
- account);
- history = json_array ();
- GNUNET_assert (NULL != history);
- GNUNET_asprintf (&credit_payto,
- "payto://x-taler-bank/localhost/%s",
- account);
- GNUNET_assert (0 ==
- pthread_mutex_lock (&h->big_lock));
- if (! ha.have_start)
- {
- pos = (0 > ha.delta)
- ? acc->in_tail
- : acc->in_head;
- }
- else
- {
- struct Transaction *t = h->transactions[ha.start_idx % h->ram_limit];
- bool overflow;
- uint64_t dir;
- bool skip = true;
-
- overflow = ( (NULL != t) && (t->row_id != ha.start_idx) );
- dir = (0 > 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 != acc) )
- {
- skip = false;
- t = h->transactions[(t->row_id + dir) % h->ram_limit];
- if ( (NULL != t) &&
- (t->row_id == ha.start_idx) )
- overflow = true; /* full circle, give up! */
- }
- if ( (NULL == t) ||
- overflow)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No transactions available, suspending request\n");
- GNUNET_free (credit_payto);
- if (GNUNET_TIME_relative_is_zero (ha.lp_timeout) &&
- (0 < ha.delta))
- {
- GNUNET_assert (0 ==
- pthread_mutex_unlock (&h->big_lock));
- return TALER_MHD_REPLY_JSON_PACK (connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal (
- "incoming_transactions",
- history));
- }
- *con_cls = &special_ptr;
- start_lp (h,
- connection,
- acc,
- ha.lp_timeout,
- LP_CREDIT);
- GNUNET_assert (0 ==
- pthread_mutex_unlock (&h->big_lock));
- json_decref (history);
- return MHD_YES;
- }
- if (skip)
- {
- /* range from application is exclusive, skip the
- matching entry */
- if (0 > 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) ha.delta,
- (unsigned long long) pos->row_id);
- else
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No credit transactions exist after given starting point\n");
- while ( (0 != ha.delta) &&
- (NULL != pos) )
- {
- json_t *trans;
- char *debit_payto;
-
- 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 > ha.delta)
- pos = pos->prev_in;
- if (0 < ha.delta)
- pos = pos->next_in;
- continue;
- }
- GNUNET_asprintf (&debit_payto,
- "payto://x-taler-bank/localhost/%s",
- pos->debit_account->account_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), // FIXME: inefficient to repeat this always here!
- GNUNET_JSON_pack_string ("debit_account",
- debit_payto),
- GNUNET_JSON_pack_data_auto ("reserve_pub",
- &pos->subject.credit.reserve_pub));
- GNUNET_assert (NULL != trans);
- 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_in;
- if (0 < ha.delta)
- pos = pos->next_in;
- }
- if ( (0 == json_array_size (history)) &&
- (! GNUNET_TIME_relative_is_zero (ha.lp_timeout)) &&
- (0 < ha.delta))
- {
- *con_cls = &special_ptr;
- start_lp (h,
- connection,
- acc,
- ha.lp_timeout,
- LP_CREDIT);
- GNUNET_assert (0 ==
- pthread_mutex_unlock (&h->big_lock));
- json_decref (history);
- return MHD_YES;
- }
- GNUNET_assert (0 ==
- pthread_mutex_unlock (&h->big_lock));
- GNUNET_free (credit_payto);
- return TALER_MHD_REPLY_JSON_PACK (connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal (
- "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
- * @return MHD result code
- */
-static MHD_RESULT
-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 == strcasecmp (method,
- MHD_HTTP_METHOD_GET))
- {
- if ( (0 == strcmp (url,
- "/history/incoming")) &&
- (NULL != account) )
- return handle_credit_history (h,
- connection,
- account,
- con_cls);
- if ( (0 == strcmp (url,
- "/history/outgoing")) &&
- (NULL != account) )
- return handle_debit_history (h,
- connection,
- account,
- con_cls);
- if (0 == strcmp (url,
- "/"))
- return handle_home_page (h,
- connection);
- }
- else if (0 == strcasecmp (method,
- MHD_HTTP_METHOD_POST))
- {
- if ( (0 == strcmp (url,
- "/admin/add-incoming")) &&
- (NULL != account) )
- return handle_admin_add_incoming (h,
- connection,
- account,
- upload_data,
- upload_data_size,
- con_cls);
- if ( (0 == strcmp (url,
- "/transfer")) &&
- (NULL != account) )
- return 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);
+ cc->ctx_cleaner (cc->ctx);
+ GNUNET_free (cc);
}
@@ -2372,31 +94,28 @@ handle_mhd_request (void *cls,
void **con_cls)
{
struct TALER_FAKEBANK_Handle *h = cls;
- char *account = NULL;
- char *end;
- MHD_RESULT ret;
(void) version;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Handling request for `%s'\n",
- url);
- if ( (strlen (url) > 1) &&
- (NULL != (end = strchr (url + 1, '/'))) )
- {
- account = GNUNET_strndup (url + 1,
- end - url - 1);
- url = end;
- }
- ret = serve (h,
- connection,
- account,
- url,
- method,
- upload_data,
- upload_data_size,
- con_cls);
- GNUNET_free (account);
- return ret;
+ if (0 == strncmp (url,
+ "/taler-integration/",
+ strlen ("/taler-integration/")))
+ {
+ url += strlen ("/taler-integration");
+ return TALER_FAKEBANK_tbi_main_ (h,
+ connection,
+ url,
+ method,
+ upload_data,
+ upload_data_size,
+ con_cls);
+ }
+ return TALER_FAKEBANK_bank_main_ (h,
+ connection,
+ url,
+ method,
+ upload_data,
+ upload_data_size,
+ con_cls);
}
@@ -2427,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);
}
@@ -2453,7 +172,12 @@ 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);
@@ -2496,7 +220,7 @@ schedule_httpd (struct TALER_FAKEBANK_Handle *h)
tv,
wrs,
wws,
- &run_mhd,
+ &TALER_FAKEBANK_run_mhd_,
h);
if (NULL != wrs)
GNUNET_NETWORK_fdset_destroy (wrs);
@@ -2513,8 +237,8 @@ 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;
@@ -2525,7 +249,12 @@ run_mhd (void *cls)
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);
}
@@ -2547,6 +276,34 @@ TALER_FAKEBANK_start2 (uint16_t port,
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)
@@ -2557,9 +314,25 @@ TALER_FAKEBANK_start2 (uint16_t port,
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;
@@ -2607,27 +380,30 @@ TALER_FAKEBANK_start2 (uint16_t port,
}
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);
if (0 == num_threads)
{
- h->mhd_bank = MHD_start_daemon (MHD_USE_DEBUG
+ h->mhd_bank = MHD_start_daemon (
+ MHD_USE_DEBUG
#if EPOLL_SUPPORT
- | MHD_USE_EPOLL
+ | MHD_USE_EPOLL
#endif
- | 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);
+ | 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);
@@ -2642,6 +418,7 @@ TALER_FAKEBANK_start2 (uint16_t port,
}
else
{
+#ifdef __linux__
h->lp_event = eventfd (0,
EFD_CLOEXEC);
if (-1 == h->lp_event)
@@ -2651,37 +428,60 @@ TALER_FAKEBANK_start2 (uint16_t port,
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,
- &lp_expiration_thread,
+ &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);
+ 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);
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-exchange-wire-gateway-client.c b/src/bank-lib/taler-exchange-wire-gateway-client.c
index ab16573a7..b0d387b71 100644
--- a/src/bank-lib/taler-exchange-wire-gateway-client.c
+++ b/src/bank-lib/taler-exchange-wire-gateway-client.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2017-2021 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
@@ -152,83 +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 enum GNUNET_GenericReturnValue
+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;
chh = NULL;
- if (MHD_HTTP_OK != http_status)
+ switch (reply->http_status)
{
- if ( (MHD_HTTP_NO_CONTENT != http_status) ||
- (TALER_EC_NONE != ec) )
+ 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++)
{
- if (0 == http_status)
- {
- fprintf (stderr,
- "Failed to obtain HTTP reply from `%s'\n",
- auth.wire_gateway_url);
- }
- else
- {
- fprintf (stderr,
- "Failed to obtain credit history from `%s': HTTP status %u (%s)\n",
- auth.wire_gateway_url,
- http_status,
- TALER_ErrorCode_get_hint (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_uri) ) )
- return GNUNET_OK;
- if ( (NULL != debit_account) &&
- (0 != strcasecmp (debit_account,
- details->debit_account_uri) ) )
- return GNUNET_OK;
-
- fprintf (stdout,
- "%llu: %s->%s (%s) over %s at %s\n",
- (unsigned long long) serial_id,
- details->debit_account_uri,
- details->credit_account_uri,
- TALER_B2S (&details->reserve_pub),
- TALER_amount2s (&details->amount),
- GNUNET_TIME_timestamp2s (details->execution_date));
- return GNUNET_OK;
+ GNUNET_SCHEDULER_shutdown ();
}
@@ -264,84 +253,71 @@ execute_credit_history (void)
/**
- * 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 enum GNUNET_GenericReturnValue
+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;
dhh = NULL;
- if (MHD_HTTP_OK != http_status)
+ switch (reply->http_status)
{
- if ( (MHD_HTTP_NO_CONTENT != http_status) ||
- (TALER_EC_NONE != ec) )
+ 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++)
{
- if (0 == http_status)
- {
- fprintf (stderr,
- "Failed to obtain HTTP reply from `%s'\n",
- auth.wire_gateway_url);
- }
- else
- {
- fprintf (stderr,
- "Failed to obtain debit history from `%s': HTTP status %u (%s)\n",
- auth.wire_gateway_url,
- http_status,
- TALER_ErrorCode_get_hint (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_uri) ) )
- return GNUNET_OK;
- if ( (NULL != debit_account) &&
- (0 != strcasecmp (debit_account,
- details->debit_account_uri) ) )
- return GNUNET_OK;
-
- fprintf (stdout,
- "%llu: %s->%s (%s) over %s at %s\n",
- (unsigned long long) serial_id,
- details->debit_account_uri,
- details->credit_account_uri,
- TALER_B2S (&details->wtid),
- TALER_amount2s (&details->amount),
- GNUNET_TIME_timestamp2s (details->execution_date));
- return GNUNET_OK;
+ GNUNET_SCHEDULER_shutdown ();
}
@@ -381,34 +357,28 @@ execute_debit_history (void)
* 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_Timestamp timestamp)
+ const struct TALER_BANK_TransferResponse *tr)
{
(void) cls;
eh = NULL;
- if (MHD_HTTP_OK != response_code)
+ 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_TIME_timestamp2s (timestamp));
+ (unsigned long long) tr->details.ok.row_id,
+ GNUNET_TIME_timestamp2s (tr->details.ok.timestamp));
global_ret = 0;
GNUNET_SCHEDULER_shutdown ();
}
@@ -488,39 +458,29 @@ execute_wire_transfer (void)
* 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_Timestamp 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;
diff --git a/src/bank-lib/taler-fakebank-run.c b/src/bank-lib/taler-fakebank-run.c
index 7fa730469..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
@@ -52,6 +52,10 @@ static struct TALER_FAKEBANK_Handle *fb;
*/
static struct GNUNET_SCHEDULER_Task *keepalive;
+/**
+ * Amount to credit an account with on /register.
+ */
+static struct TALER_Amount signup_bonus;
/**
* Stop the process.
@@ -101,8 +105,10 @@ run (void *cls,
const struct GNUNET_CONFIGURATION_Handle *cfg)
{
unsigned long long port = 8082;
- unsigned long long ram = 1024 * 1024 * 128; /* 128 M entries */
+ unsigned long long ram = 1024 * 128; /* 128 k entries */
char *currency_string;
+ char *hostname;
+ char *exchange_url;
(void) cls;
(void) args;
@@ -125,6 +131,23 @@ run (void *cls,
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",
@@ -142,17 +165,37 @@ run (void *cls,
go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
TALER_MHD_setup (go);
}
- fb = TALER_FAKEBANK_start2 ((uint16_t) port,
+ 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);
+ num_threads,
+ &signup_bonus);
+ GNUNET_free (hostname);
+ GNUNET_free (exchange_url);
+ GNUNET_free (currency_string);
if (NULL == fb)
{
GNUNET_break (0);
ret = EXIT_FAILURE;
return;
}
- GNUNET_free (currency_string);
keepalive = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
&keepalive_task,
NULL);
@@ -183,16 +226,24 @@ main (int argc,
"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
};
-
- if (GNUNET_OK !=
- GNUNET_PROGRAM_run (argc, argv,
- "taler-fakebank-run",
- "Runs the fakebank",
- options,
- &run,
- NULL))
+ enum GNUNET_GenericReturnValue iret;
+
+ 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.sh b/src/bank-lib/test_bank.sh
index 46be326b4..5ee2bd836 100755
--- a/src/bank-lib/test_bank.sh
+++ b/src/bank-lib/test_bank.sh
@@ -1,19 +1,20 @@
#!/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
+ echo "$1"
exit 77
}
# Cleanup to run whenever we exit
function cleanup()
{
- for n in `jobs -p`
+ for n in $(jobs -p)
do
- kill $n 2> /dev/null || true
+ kill "$n" 2> /dev/null || true
done
wait
}
@@ -23,21 +24,30 @@ trap cleanup EXIT
echo -n "Launching bank..."
-taler-fakebank-run -c test_bank.conf -L DEBUG &> bank.log &
+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`
+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
+ wget \
+ --tries=1 \
+ --timeout=1 \
+ http://localhost:8899/ \
+ -o /dev/null \
+ -O /dev/null \
+ >/dev/null \
+ || continue
OK=1
break
done
-if [ 1 != $OK ]
+if [ 1 != "$OK" ]
then
exit_skip "Failed to launch services (bank)"
fi
@@ -47,31 +57,40 @@ echo "OK"
echo -n "Making wire transfer to exchange ..."
taler-exchange-wire-gateway-client \
- -b http://localhost:8899/exchange/ \
+ -b http://localhost:8899/accounts/exchange/taler-wire-gateway/ \
-S 0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00 \
- -D payto://x-taler-bank/localhost:8899/user \
+ -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/exchange/ -i | grep TESTKUDOS:4 > /dev/null
+./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/exchange/ \
+ -b http://localhost:8899/accounts/exchange/taler-wire-gateway/ \
-S 0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00 \
- -C payto://x-taler-bank/localhost:8899/merchant \
- -a TESTKUDOS:2 > /dev/null
+ -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/exchange/ -o | grep TESTKUDOS:2 > /dev/null
+./taler-exchange-wire-gateway-client \
+ -b http://localhost:8899/accounts/exchange/taler-wire-gateway/ \
+ -o \
+ | grep TESTKUDOS:2 \
+ > /dev/null
echo " OK"
diff --git a/src/benchmark/.gitignore b/src/benchmark/.gitignore
index 45e150087..a1b4711e7 100644
--- a/src/benchmark/.gitignore
+++ b/src/benchmark/.gitignore
@@ -1,2 +1,3 @@
taler-bank-benchmark
taler-aggregator-benchmark
+*.edited
diff --git a/src/benchmark/Makefile.am b/src/benchmark/Makefile.am
index 7c5ceefd6..c82584626 100644
--- a/src/benchmark/Makefile.am
+++ b/src/benchmark/Makefile.am
@@ -15,6 +15,7 @@ bin_PROGRAMS = \
taler-bank-benchmark \
taler-exchange-benchmark
+
taler_aggregator_benchmark_SOURCES = \
taler-aggregator-benchmark.c
taler_aggregator_benchmark_LDADD = \
@@ -32,11 +33,13 @@ taler_bank_benchmark_SOURCES = \
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 \
@@ -54,6 +57,7 @@ taler_exchange_benchmark_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 \
-lgnunetjson \
-lgnunetcurl \
-lgnunetutil \
@@ -61,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/bank-benchmark.conf b/src/benchmark/bank-benchmark.conf
deleted file mode 100644
index 1942d551b..000000000
--- a/src/benchmark/bank-benchmark.conf
+++ /dev/null
@@ -1,128 +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
-signkey_legal_duration = 2 years
-# how long do we provide to clients denomination and signing keys
-# ahead of time?
-# Keep it short so the test runs fast.
-lookahead_sign = 12h
-# 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/"
-
-WIREWATCH_IDLE_SLEEP_INTERVAL = 1500 ms
-
-[exchange-offline]
-MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/master.priv
-
-[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"
-enable_debit = YES
-enable_credit = YES
-
-[exchange-accountcredentials-2]
-# What is the bank account (with the "Taler Bank" demo system)? Must end with "/".
-WIRE_GATEWAY_URL = http://localhost:8082/Exchange/
-# Authentication information for basic authentication
-WIRE_GATEWAY_AUTH_METHOD = "basic"
-username = Exchange
-password = x
-
-
-
-
-# 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/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 5199ee872..000000000
--- a/src/benchmark/benchmark.conf
+++ /dev/null
@@ -1,126 +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]
-
-SIGNKEY_LEGAL_DURATION = 2 years
-
-# 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/"
-
-AGGREGATOR_SHARD_SIZE = 67108864
-#AGGREGATOR_SHARD_SIZE = 2147483648
-
-
-
-WIREWATCH_IDLE_SLEEP_INTERVAL = 5 ms
-
-[exchange-offline]
-MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange/offline-keys/master.priv
-
-[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-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/Exchange/
-WIRE_GATEWAY_AUTH_METHOD = "basic"
-USERNAME = Exchange
-PASSWORD = x
-
-
-# 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
index 411921000..228d050e4 100644
--- a/src/benchmark/taler-aggregator-benchmark.c
+++ b/src/benchmark/taler-aggregator-benchmark.c
@@ -76,7 +76,7 @@ static struct GNUNET_SCHEDULER_Task *task;
/**
* Hash of the denomination.
*/
-static struct TALER_DenominationHash h_denom_pub;
+static struct TALER_DenominationHashP h_denom_pub;
/**
* "signature" to use for the coin(s).
@@ -144,29 +144,6 @@ make_amount (unsigned int val,
/**
- * 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_amountN (unsigned int val,
- unsigned int frac,
- struct TALER_AmountNBO *out)
-{
- struct TALER_Amount in;
-
- make_amount (val,
- frac,
- &in);
- TALER_amount_hton (out,
- &in);
-}
-
-
-/**
* Create random-ish timestamp.
*
* @return time stamp between start and end
@@ -223,12 +200,12 @@ struct Merchant
* the exchange from the detailed wire data provided by the
* merchant.
*/
- struct TALER_MerchantWireHash h_wire;
+ struct TALER_MerchantWireHashP h_wire;
/**
* Salt used when computing @e h_wire.
*/
- struct TALER_WireSalt wire_salt;
+ struct TALER_WireSaltP wire_salt;
/**
* Account information for the merchant.
@@ -249,7 +226,7 @@ struct Deposit
* Hash over the proposal data between merchant and customer
* (remains unknown to the Exchange).
*/
- struct TALER_PrivateContractHash h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
};
@@ -274,7 +251,7 @@ add_refund (const struct Merchant *m,
r.details.rtransaction_id = 42;
make_amount (0, 5000000, &r.details.refund_amount);
make_amount (0, 5, &r.details.refund_fee);
- if (0 <=
+ if (0 >=
plugin->insert_refund (plugin->cls,
&r))
{
@@ -297,15 +274,23 @@ static bool
add_deposit (const struct Merchant *m)
{
struct Deposit d;
- struct TALER_EXCHANGEDB_Deposit deposit;
+ struct TALER_EXCHANGEDB_CoinDepositInformation deposit;
+ struct TALER_EXCHANGEDB_BatchDeposit bd = {
+ .cdis = &deposit,
+ .num_cdis = 1
+ };
uint64_t known_coin_id;
- struct TALER_DenominationHash dph;
- struct TALER_AgeHash agh;
+ 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,
@@ -321,24 +306,40 @@ add_deposit (const struct Merchant *m)
}
deposit.coin = d.coin;
RANDOMIZE (&deposit.csig);
- deposit.merchant_pub = m->merchant_pub;
- deposit.h_contract_terms = d.h_contract_terms;
- deposit.wire_salt = m->wire_salt;
- deposit.receiver_wire_account = m->payto_uri;
- deposit.timestamp = random_time ();
- deposit.refund_deadline = random_time ();
- deposit.wire_deadline = random_time ();
+ 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);
- make_amount (0, 5, &deposit.deposit_fee);
- if (0 >=
- plugin->insert_deposit (plugin->cls,
- random_time (),
- &deposit))
+
{
- GNUNET_break (0);
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- return false;
+ 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))
@@ -430,7 +431,7 @@ run (void *cls,
const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *c)
{
- struct TALER_EXCHANGEDB_DenominationKeyInformationP issue;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
(void) cls;
(void) args;
@@ -465,48 +466,50 @@ run (void *cls,
}
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
NULL);
+ memset (&issue,
+ 0,
+ sizeof (issue));
RANDOMIZE (&issue.signature);
- issue.properties.purpose.purpose = htonl (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY);
- issue.properties.purpose.size = htonl (sizeof (issue.properties));
- RANDOMIZE (&issue.properties.master);
- issue.properties.start
- = GNUNET_TIME_timestamp_hton (start);
- issue.properties.expire_withdraw
- = GNUNET_TIME_timestamp_hton (
- GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (start.abs_time,
- GNUNET_TIME_UNIT_DAYS)));
- issue.properties.expire_deposit
- = GNUNET_TIME_timestamp_hton (end);
- issue.properties.expire_legal
- = GNUNET_TIME_timestamp_hton (
- GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (end.abs_time,
- GNUNET_TIME_UNIT_YEARS)));
+ 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_CoinPubHash c_hash;
+ struct TALER_CoinPubHashP c_hash;
struct TALER_PlanchetDetail pd;
struct TALER_BlindedDenominationSignature bds;
- union TALER_DenominationBlindingKeyP bks;
+ 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,
- TALER_DENOMINATION_RSA,
+ 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_amountN (2, 0, &issue.properties.value);
- make_amountN (0, 5, &issue.properties.fee_withdraw);
- make_amountN (0, 5, &issue.properties.fee_deposit);
- make_amountN (0, 5, &issue.properties.fee_refresh);
- make_amountN (0, 5, &issue.properties.fee_refund);
- issue.properties.denom_hash = 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,
@@ -518,25 +521,49 @@ run (void *cls,
return;
}
- TALER_blinding_secret_create (&bks);
+ 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, /* FIXME-oec */
+ NULL,
+ &hac,
&coin_pub,
+ alg_values,
&c_hash,
- &pd.coin_ev,
- &pd.coin_ev_size));
+ &pd.blinded_planchet));
GNUNET_assert (GNUNET_OK ==
TALER_denom_sign_blinded (&bds,
&pk,
- pd.coin_ev,
- pd.coin_ev_size));
- GNUNET_free (pd.coin_ev);
+ 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);
@@ -544,7 +571,7 @@ run (void *cls,
}
{
- struct TALER_Amount wire_fee;
+ struct TALER_WireFeeSet fees;
struct TALER_MasterSignatureP master_sig;
unsigned int year;
struct GNUNET_TIME_Timestamp ws;
@@ -555,7 +582,8 @@ run (void *cls,
{
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, &wire_fee);
+ make_amount (0, 5, &fees.wire);
+ make_amount (0, 5, &fees.closing);
memset (&master_sig,
0,
sizeof (master_sig));
@@ -564,8 +592,7 @@ run (void *cls,
"x-taler-bank",
ws,
we,
- &wire_fee,
- &wire_fee,
+ &fees,
&master_sig))
{
GNUNET_break (0);
diff --git a/src/benchmark/taler-bank-benchmark.c b/src/benchmark/taler-bank-benchmark.c
index 4d7dbe35e..528798424 100644
--- a/src/benchmark/taler-bank-benchmark.c
+++ b/src/benchmark/taler-bank-benchmark.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014-2021 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
@@ -38,55 +38,12 @@
#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
-};
-
-
-/**
- * What mode should the benchmark run in?
- */
-enum BenchmarkMode
-{
- /**
- * Run as client against the bank.
- */
- MODE_CLIENT = 1,
-
- /**
- * Run the bank.
- */
- MODE_BANK = 2,
-
- /**
- * Run both, for a local benchmark.
- */
- MODE_BOTH = 3,
-};
-
+#define SHARD_SIZE "1024"
/**
- * Hold information regarding which bank has the exchange account.
+ * Credentials to use for the benchmark.
*/
-static const struct TALER_EXCHANGEDB_AccountInfo *exchange_bank_account;
-
-/**
- * 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.
@@ -94,26 +51,14 @@ static struct GNUNET_TIME_Relative duration;
static struct TALER_TESTING_Command *all_commands;
/**
- * Dummy keepalive task.
- */
-static struct GNUNET_SCHEDULER_Task *keepalive;
-
-/**
* Name of our configuration file.
*/
static char *cfg_filename;
/**
* Use the fakebank instead of LibEuFin.
- * NOTE: LibEuFin not yet supported! Set
- * to 0 once we do support it!
*/
-static int use_fakebank = 1;
-
-/**
- * Launch taler-exchange-wirewatch.
- */
-static int start_wirewatch;
+static int use_fakebank;
/**
* Verbosity level.
@@ -121,12 +66,6 @@ static int start_wirewatch;
static unsigned int verbose;
/**
- * Size of the transaction history the fakebank
- * should keep in RAM.
- */
-static unsigned long long history_size = 65536;
-
-/**
* How many reserves we want to create per client.
*/
static unsigned int howmany_reserves = 1;
@@ -137,9 +76,9 @@ static unsigned int howmany_reserves = 1;
static unsigned int howmany_clients = 1;
/**
- * How many bank worker threads do we want to create.
+ * How many wirewatch processes do we want to create.
*/
-static unsigned int howmany_threads;
+static unsigned int start_wirewatch;
/**
* Log level used during the run.
@@ -152,25 +91,15 @@ static char *loglev;
static char *logfile;
/**
- * Benchmarking mode (run as client, exchange, both) as string.
- */
-static char *mode_str;
-
-/**
- * Benchmarking mode (run as client, bank, both).
- */
-static enum BenchmarkMode mode;
-
-/**
- * Don't kill exchange/fakebank/wirewatch until
- * requested by the user explicitly.
+ * Configuration.
*/
-static int linger;
+static struct GNUNET_CONFIGURATION_Handle *cfg;
/**
- * Configuration.
+ * Section with the configuration data for the exchange
+ * bank account.
*/
-static struct GNUNET_CONFIGURATION_Handle *cfg;
+static char *exchange_bank_section;
/**
* Currency used.
@@ -232,10 +161,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,
@@ -265,37 +194,39 @@ run (void *cls,
(void) cls;
len = howmany_reserves + 2;
- all_commands = GNUNET_new_array (len,
- struct TALER_TESTING_Command);
+ 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;
- char *user_payto_uri;
-
- // FIXME: vary user accounts more...
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "benchmark",
- "USER_PAYTO_URI",
- &user_payto_uri));
+
GNUNET_asprintf (&create_reserve_label,
"createreserve-%u",
j);
- all_commands[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,
- exchange_bank_account->auth,
- add_label (user_payto_uri)));
+ &cred.ba_admin,
+ cred.user42_payto));
}
GNUNET_free (total_reserve_amount);
- all_commands[howmany_reserves]
+ all_commands[1 + howmany_reserves]
= TALER_TESTING_cmd_stat (timings);
- all_commands[howmany_reserves + 1]
+ all_commands[1 + howmany_reserves + 1]
= TALER_TESTING_cmd_end ();
TALER_TESTING_run2 (is,
all_commands,
@@ -312,15 +243,11 @@ launch_clients (void)
enum GNUNET_GenericReturnValue result = GNUNET_OK;
pid_t cpids[howmany_clients];
- start_time = GNUNET_TIME_absolute_get ();
if (1 == howmany_clients)
{
/* do everything in this process */
- result = TALER_TESTING_setup (&run,
- NULL,
- cfg,
- NULL,
- GNUNET_NO);
+ result = TALER_TESTING_loop (&run,
+ NULL);
if (verbose)
print_stats ();
return result;
@@ -334,11 +261,8 @@ launch_clients (void)
GNUNET_log_setup ("benchmark-worker",
NULL == loglev ? "INFO" : loglev,
logfile);
- result = TALER_TESTING_setup (&run,
- NULL,
- cfg,
- NULL,
- GNUNET_NO);
+ result = TALER_TESTING_loop (&run,
+ NULL);
if (verbose)
print_stats ();
if (GNUNET_OK != result)
@@ -388,282 +312,109 @@ again:
/**
- * Stop the fakebank.
- *
- * @param cls fakebank handle
- */
-static void
-stop_fakebank (void *cls)
-{
- struct TALER_FAKEBANK_Handle *fakebank = cls;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Stopping fakebank\n");
- TALER_FAKEBANK_stop (fakebank);
- GNUNET_SCHEDULER_cancel (keepalive);
- keepalive = NULL;
-}
-
-
-/**
- * Dummy task that is never run.
- */
-static void
-never_task (void *cls)
-{
- (void) cls;
- GNUNET_assert (0);
-}
-
-
-/**
- * Start the fakebank.
- *
- * @param cls NULL
- */
-static void
-launch_fakebank (void *cls)
-{
- struct TALER_FAKEBANK_Handle *fakebank;
- unsigned long long pnum;
-
- (void) cls;
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_number (cfg,
- "bank",
- "HTTP_PORT",
- &pnum))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "bank",
- "HTTP_PORT",
- "must be valid port number");
- return;
- }
- fakebank
- = TALER_FAKEBANK_start2 ((uint16_t) pnum,
- currency,
- history_size,
- howmany_threads);
- if (NULL == fakebank)
- {
- GNUNET_break (0);
- return;
- }
- keepalive
- = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
- &never_task,
- NULL);
- GNUNET_SCHEDULER_add_shutdown (&stop_fakebank,
- fakebank);
-}
-
-
-/**
* Run the benchmark in parallel in many (client) processes
* and summarize result.
*
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
parallel_benchmark (void)
{
enum GNUNET_GenericReturnValue result = GNUNET_OK;
- pid_t fakebank = -1;
- struct GNUNET_OS_Process *bankd = NULL;
- struct GNUNET_OS_Process *wirewatch = NULL;
+ struct GNUNET_OS_Process *wirewatch[GNUNET_NZL (start_wirewatch)];
- if ( (MODE_BANK == mode) ||
- (MODE_BOTH == mode) )
+ memset (wirewatch,
+ 0,
+ sizeof (wirewatch));
+ /* start exchange wirewatch */
+ for (unsigned int w = 0; w<start_wirewatch; w++)
{
- if (use_fakebank)
- {
- unsigned long long pnum;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_number (cfg,
- "bank",
- "HTTP_PORT",
- &pnum))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "bank",
- "HTTP_PORT",
- "must be valid port number");
- return GNUNET_SYSERR;
- }
- /* start fakebank */
- fakebank = fork ();
- if (0 == fakebank)
- {
- GNUNET_log_setup ("benchmark-fakebank",
- NULL == loglev ? "INFO" : loglev,
- logfile);
- GNUNET_SCHEDULER_run (&launch_fakebank,
- NULL);
- exit (0);
- }
- if (-1 == fakebank)
- {
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "fork");
- return GNUNET_SYSERR;
- }
- /* wait for fakebank to be ready */
- {
- char *bank_url;
- int ret;
-
- GNUNET_asprintf (&bank_url,
- "http://localhost:%u/",
- (unsigned int) (uint16_t) pnum);
- ret = TALER_TESTING_wait_httpd_ready (bank_url);
- GNUNET_free (bank_url);
- if (0 != ret)
- {
- int wstatus;
-
- kill (fakebank,
- SIGTERM);
- if (fakebank !=
- waitpid (fakebank,
- &wstatus,
- 0))
- {
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "waitpid");
- }
- fakebank = -1;
- exit (ret);
- }
- }
- }
- else
+ 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,
- "FIXME: launching LibEuFin not yet supported\n");
- bankd = NULL; // FIXME
- return GNUNET_SYSERR;
- }
-
- {
- struct GNUNET_OS_Process *dbinit;
-
- dbinit = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-exchange-dbinit",
- "taler-exchange-dbinit",
- "-c", cfg_filename,
- "-r",
- NULL);
- GNUNET_break (GNUNET_OK ==
- GNUNET_OS_process_wait (dbinit));
- GNUNET_OS_process_destroy (dbinit);
- }
- if (start_wirewatch)
- {
- /* start exchange wirewatch */
- wirewatch = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- "taler-exchange-wirewatch",
- "taler-exchange-wirewatch",
- "-c", cfg_filename,
- NULL);
- if (NULL == wirewatch)
+ "Failed to launch wirewatch, aborting benchmark\n");
+ for (unsigned int x = 0; x<w; x++)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to launch wirewatch, aborting benchmark\n");
- if (-1 != fakebank)
- {
- int wstatus;
-
- kill (fakebank,
- SIGTERM);
- if (fakebank !=
- waitpid (fakebank,
- &wstatus,
- 0))
- {
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "waitpid");
- }
- fakebank = -1;
- }
- if (NULL != bankd)
- {
- GNUNET_OS_process_kill (bankd,
- SIGTERM);
- GNUNET_OS_process_destroy (bankd);
- bankd = NULL;
- }
- return GNUNET_SYSERR;
+ 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;
}
}
-
- if ( (MODE_CLIENT == mode) ||
- (MODE_BOTH == mode) )
- result = launch_clients ();
- if ( (GNUNET_YES == linger) ||
- (MODE_BANK == mode) )
+ result = launch_clients ();
+ /* Ensure wirewatch runs to completion! */
+ if (0 != start_wirewatch)
{
- printf ("Press ENTER to stop!\n");
- if (MODE_BANK != mode)
- duration = GNUNET_TIME_absolute_get_duration (start_time);
- (void) getchar ();
- if (MODE_BANK == mode)
- duration = GNUNET_TIME_absolute_get_duration (start_time);
- }
-
- if ( (MODE_BANK == mode) ||
- (MODE_BOTH == mode) )
- {
- if (NULL != 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++)
{
- /* stop wirewatch */
GNUNET_break (0 ==
- GNUNET_OS_process_kill (wirewatch,
+ GNUNET_OS_process_kill (wirewatch[w],
SIGTERM));
GNUNET_break (GNUNET_OK ==
- GNUNET_OS_process_wait (wirewatch));
- GNUNET_OS_process_destroy (wirewatch);
- wirewatch = NULL;
- }
- /* stop fakebank */
- if (-1 != fakebank)
- {
- int wstatus;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Telling fakebank to shut down\n");
- kill (fakebank,
- SIGTERM);
- if (fakebank !=
- waitpid (fakebank,
- &wstatus,
- 0))
- {
- GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
- "waitpid");
- }
- else
- {
- if ( (! WIFEXITED (wstatus)) ||
- (0 != WEXITSTATUS (wstatus)) )
- {
- GNUNET_break (0);
- result = GNUNET_SYSERR;
- }
- }
- fakebank = -1;
- }
- if (NULL != bankd)
- {
- GNUNET_OS_process_kill (bankd,
- SIGTERM);
- GNUNET_OS_process_destroy (bankd);
+ 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;
}
@@ -683,56 +434,44 @@ main (int argc,
struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_option_mandatory (
GNUNET_GETOPT_option_cfgfile (&cfg_filename)),
-#if FIXME_SUPPORT_LIBEUFIN
GNUNET_GETOPT_option_flag ('f',
"fakebank",
- "start a fakebank instead of the Python bank",
+ "we are using fakebank",
&use_fakebank),
-#endif
GNUNET_GETOPT_option_help ("taler-bank benchmark"),
- GNUNET_GETOPT_option_flag ('K',
- "linger",
- "linger around until key press",
- &linger),
GNUNET_GETOPT_option_string ('l',
"logfile",
"LF",
"will log to file LF",
&logfile),
GNUNET_GETOPT_option_loglevel (&loglev),
- GNUNET_GETOPT_option_string ('m',
- "mode",
- "MODE",
- "run as bank, client or both",
- &mode_str),
GNUNET_GETOPT_option_uint ('p',
"worker-parallelism",
"NPROCS",
"How many client processes we should run",
&howmany_clients),
- GNUNET_GETOPT_option_uint ('P',
- "service-parallelism",
- "NTHREADS",
- "How many service threads we should create",
- &howmany_threads),
GNUNET_GETOPT_option_uint ('r',
"reserves",
"NRESERVES",
"How many reserves per client we should create",
&howmany_reserves),
- GNUNET_GETOPT_option_ulong ('s',
- "size",
- "HISTORY_SIZE",
- "Maximum history size kept in memory by the fakebank",
- &history_size),
+ 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_flag ('w',
+ GNUNET_GETOPT_option_uint ('w',
"wirewatch",
- "run taler-exchange-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");
@@ -745,43 +484,15 @@ main (int argc,
GNUNET_free (cfg_filename);
if (GNUNET_NO == result)
return 0;
- return BAD_CLI_ARG;
+ 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",
- NULL == loglev ? "INFO" : loglev,
+ loglev,
logfile);
- if (history_size < 10)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "History size too small, this can hardly work\n");
- return BAD_CLI_ARG;
- }
- if (NULL == mode_str)
- mode = MODE_BOTH;
- else if (0 == strcasecmp (mode_str,
- "bank"))
- mode = MODE_BANK;
- else if (0 == strcasecmp (mode_str,
- "client"))
- mode = MODE_CLIENT;
- else if (0 == strcasecmp (mode_str,
- "both"))
- mode = MODE_BOTH;
- else
- {
- TALER_LOG_ERROR ("Unknown mode given: '%s'\n",
- mode_str);
- GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
- }
- if (NULL == cfg_filename)
- cfg_filename = GNUNET_CONFIGURATION_default_filename ();
- if (NULL == cfg_filename)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Can't find default configuration file.\n");
- return EXIT_NOTCONFIGURED;
- }
cfg = GNUNET_CONFIGURATION_create ();
if (GNUNET_OK !=
GNUNET_CONFIGURATION_load (cfg,
@@ -789,7 +500,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,
@@ -797,52 +508,30 @@ main (int argc,
{
GNUNET_CONFIGURATION_destroy (cfg);
GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
- }
- if (MODE_BANK != mode)
- {
- if (howmany_clients > 10240)
- {
- TALER_LOG_ERROR ("-p option value given is too large\n");
- return BAD_CLI_ARG;
- }
- 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_NOTCONFIGURED;
}
if (GNUNET_OK !=
- TALER_EXCHANGEDB_load_accounts (cfg,
- TALER_EXCHANGEDB_ALO_AUTHDATA
- | TALER_EXCHANGEDB_ALO_CREDIT))
+ 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,
- "Configuration fails to provide exchange bank details\n");
+ "Required bank credentials not given in configuration\n");
GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
+ return EXIT_NOTCONFIGURED;
}
- exchange_bank_account
- = TALER_EXCHANGEDB_find_account_by_method ("x-taler-bank");
- if (NULL == exchange_bank_account)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "No bank account for `x-taler-bank` given in configuration\n");
- GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
- }
- result = parallel_benchmark ();
- TALER_EXCHANGEDB_unload_accounts ();
- GNUNET_CONFIGURATION_destroy (cfg);
- GNUNET_free (cfg_filename);
+ struct GNUNET_TIME_Absolute start_time;
- if (MODE_BANK == mode)
- {
- /* If we're the bank, we're done now. No need to print results. */
- return (GNUNET_OK == result) ? 0 : result;
+ start_time = GNUNET_TIME_absolute_get ();
+ result = parallel_benchmark ();
+ duration = GNUNET_TIME_absolute_get_duration (start_time);
}
if (GNUNET_OK == result)
@@ -858,16 +547,19 @@ main (int argc,
howmany_clients,
GNUNET_STRINGS_relative_time_to_string (duration,
GNUNET_YES));
- 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);
+ 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", \
+ "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
@@ -878,5 +570,7 @@ main (int argc,
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 25c3b0455..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,25 +28,7 @@
#include <microhttpd.h>
#include <sys/resource.h>
#include "taler_util.h"
-#include "taler_signatures.h"
-#include "taler_exchangedb_lib.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
@@ -55,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.
- */
-static const struct TALER_EXCHANGEDB_AccountInfo *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.
+ * Credentials to use for the benchmark.
*/
-static struct GNUNET_TIME_Relative duration;
+static struct TALER_TESTING_Credentials cred;
/**
* Array of all the commands the benchmark is running.
@@ -117,16 +54,6 @@ static struct TALER_TESTING_Command *all_commands;
static char *cfg_filename;
/**
- * Exit code.
- */
-static enum GNUNET_GenericReturnValue 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;
@@ -147,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;
@@ -162,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;
@@ -182,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.
@@ -212,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[] = {
@@ -256,14 +163,15 @@ cmd_transfer_to_exchange (const char *label,
return TALER_TESTING_cmd_admin_add_incoming_retry (
TALER_TESTING_cmd_admin_add_incoming (label,
amount,
- exchange_bank_account->auth,
- user_payto_uri));
+ &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
*/
@@ -298,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_set_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,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);
}
@@ -359,24 +277,25 @@ run (void *cls,
wl = add_label (withdraw_label);
GNUNET_asprintf (&order_enc,
"{\"nonce\": %llu}",
- ((unsigned long long) i) + (howmany_coins
- * (unsigned long long) 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;
@@ -395,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
@@ -419,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,
@@ -437,7 +356,6 @@ run (void *cls,
GNUNET_free (amount_4);
GNUNET_free (amount_5);
GNUNET_free (withdraw_fee_str);
- result = GNUNET_OK;
}
@@ -454,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,
@@ -473,45 +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->auth->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.
*
@@ -520,363 +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)
{
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)
+ {
+ result = TALER_TESTING_loop (main_cb,
+ main_cb_cls);
+ print_stats ();
+ }
+ else
{
- if (use_fakebank)
+ 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_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_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_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_DISK_PF_BLOCKING_RW)));
-
- exchange_slave = GNUNET_OS_start_process (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)
+ /* collect all children */
+ for (unsigned int i = 0; i<howmany_clients; i++)
{
- 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
- {
- 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);
-
- /* 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));
+ int wstatus;
- 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)) ||
@@ -886,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;
}
@@ -909,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");
@@ -970,25 +548,15 @@ main (int argc,
GNUNET_free (cfg_filename);
if (GNUNET_NO == result)
return 0;
- return BAD_CLI_ARG;
+ 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
- {
- TALER_LOG_ERROR ("Unknown mode given: '%s'\n", mode_str);
- GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
- }
if (NULL == cfg_filename)
cfg_filename = GNUNET_CONFIGURATION_default_filename ();
if (NULL == cfg_filename)
@@ -1004,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,
@@ -1012,177 +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;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (cfg,
- "benchmark",
- "USER_PAYTO_URI",
- &user_payto_uri))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "benchmark",
- "USER_PAYTO_URI");
- GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
+ return EXIT_INVALIDARGUMENT;
}
if (GNUNET_OK !=
- TALER_EXCHANGEDB_load_accounts (cfg,
- TALER_EXCHANGEDB_ALO_AUTHDATA
- | TALER_EXCHANGEDB_ALO_CREDIT))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Configuration fails to provide exchange bank details\n");
- GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
- }
-
- exchange_bank_account
- = TALER_EXCHANGEDB_find_account_by_method ("x-taler-bank");
- if (NULL == exchange_bank_account)
+ 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,
- "No bank account for `x-taler-bank` given in configuration\n");
+ "Required bank credentials not given in configuration\n");
GNUNET_free (cfg_filename);
- return BAD_CONFIG_FILE;
+ return EXIT_NOTCONFIGURED;
}
- if ( (MODE_EXCHANGE == mode) || (MODE_BOTH == mode) )
- {
- struct GNUNET_OS_Process *compute_wire_response;
- compute_wire_response
- = GNUNET_OS_start_process (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;
- }
+ struct GNUNET_TIME_Absolute start_time;
- 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);
- TALER_EXCHANGEDB_unload_accounts ();
- 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 f60a3806d..c8f8761aa 100644
--- a/src/curl/Makefile.am
+++ b/src/curl/Makefile.am
@@ -17,7 +17,7 @@ libtalercurl_la_SOURCES = \
libtalercurl_la_LIBADD = \
-lgnunetcurl \
-lgnunetutil \
- $(LIBGNURLCURL_LIBS) \
+ -lcurl \
-ljansson \
-lz \
-lm \
diff --git a/src/curl/curl.c b/src/curl/curl.c
index 5009fa3cf..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-2021 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
@@ -30,6 +30,58 @@
#endif
+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,
@@ -46,7 +98,8 @@ TALER_curl_easy_post (struct TALER_CURL_PostContext *ctx,
return GNUNET_SYSERR;
}
slen = strlen (str);
-#if TALER_CURL_COMPRESS_BODIES
+ if (TALER_CURL_COMPRESS_BODIES &&
+ (! ctx->disable_compression) )
{
Bytef *cbuf;
uLongf cbuf_size;
@@ -68,18 +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,
diff --git a/src/exchange-tools/Makefile.am b/src/exchange-tools/Makefile.am
index b233a4b3a..955544564 100644
--- a/src/exchange-tools/Makefile.am
+++ b/src/exchange-tools/Makefile.am
@@ -15,8 +15,7 @@ endif
bin_PROGRAMS = \
taler-auditor-offline \
taler-exchange-offline \
- taler-exchange-dbinit \
- taler-crypto-worker
+ taler-exchange-dbinit
taler_exchange_offline_SOURCES = \
taler-exchange-offline.c
@@ -25,6 +24,7 @@ taler_exchange_offline_LDADD = \
$(top_builddir)/src/lib/libtalerexchange.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
-lgnunetjson \
-lgnunetcurl \
-ljansson \
@@ -38,6 +38,7 @@ taler_auditor_offline_LDADD = \
$(top_builddir)/src/lib/libtalerexchange.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/extensions/libtalerextensions.la \
-lgnunetjson \
-lgnunetcurl \
-ljansson \
@@ -58,20 +59,6 @@ taler_exchange_dbinit_CPPFLAGS = \
-I$(top_srcdir)/src/pq/ \
$(POSTGRESQL_CPPFLAGS)
-taler_crypto_worker_SOURCES = \
- taler-crypto-worker.c
-taler_crypto_worker_LDADD = \
- $(top_builddir)/src/util/libtalerutil.la \
- $(top_builddir)/src/json/libtalerjson.la \
- -lgnunetutil \
- -lgnunetjson \
- -ljansson \
- -lpthread \
- $(LIBGCRYPT_LIBS) \
- $(XLIB)
-
-
-
# Testcases
diff --git a/src/exchange-tools/exchange-offline.conf b/src/exchange-tools/exchange-offline.conf
index cfd5c98ef..020eb34ba 100644
--- a/src/exchange-tools/exchange-offline.conf
+++ b/src/exchange-tools/exchange-offline.conf
@@ -3,10 +3,10 @@
[exchange-offline]
# Where do we store the offline master private key of the exchange?
-MASTER_PRIV_FILE = ${TALER_DATA_HOME}/exchange-offline/master.priv
+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
+SECM_TOFU_FILE = ${TALER_DATA_HOME}exchange-offline/secm_tofus.pub
# Base32-encoded public key of the RSA helper.
# SECM_DENOM_PUBKEY =
diff --git a/src/exchange-tools/taler-auditor-offline.c b/src/exchange-tools/taler-auditor-offline.c
index afde705b6..8c280d46b 100644
--- a/src/exchange-tools/taler-auditor-offline.c
+++ b/src/exchange-tools/taler-auditor-offline.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020-2021 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -174,7 +174,7 @@ static struct DenominationAddRequest *dar_tail;
/**
* Handle to the exchange, used to request /keys.
*/
-static struct TALER_EXCHANGE_Handle *exchange;
+static struct TALER_EXCHANGE_GetKeysHandle *exchange;
/**
@@ -219,7 +219,7 @@ do_shutdown (void *cls)
}
if (NULL != exchange)
{
- TALER_EXCHANGE_disconnect (exchange);
+ TALER_EXCHANGE_get_keys_cancel (exchange);
exchange = NULL;
}
if (NULL != nxt)
@@ -388,14 +388,15 @@ load_offline_key (int do_create)
* add operation result.
*
* @param cls closure with a `struct DenominationAddRequest`
- * @param hr HTTP response data
+ * @param adr response data
*/
static void
denomination_add_cb (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+ 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)
{
@@ -430,7 +431,7 @@ upload_denomination_add (const char *exchange_url,
const json_t *value)
{
struct TALER_AuditorSignatureP auditor_sig;
- struct TALER_DenominationHash h_denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
struct DenominationAddRequest *dar;
const char *err_name;
unsigned int err_line;
@@ -644,30 +645,24 @@ do_upload (char *const *args)
* a particular exchange and what keys the exchange is using.
*
* @param cls closure with the `char **` remaining args
- * @param hr HTTP response data
- * @param keys information about the various keys used
- * by the exchange, NULL if /keys failed
- * @param compat protocol compatibility information
+ * @param kr response data
+ * @param keys key data from the exchange
*/
static void
keys_cb (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_EXCHANGE_Keys *keys,
- enum TALER_EXCHANGE_VersionCompatibility compat)
+ const struct TALER_EXCHANGE_KeysResponse *kr,
+ struct TALER_EXCHANGE_Keys *keys)
{
char *const *args = cls;
- (void) keys;
- (void) compat;
- switch (hr->http_status)
+ exchange = NULL;
+ switch (kr->hr.http_status)
{
case MHD_HTTP_OK:
- if (! json_is_object (hr->reply))
+ if (NULL == kr->hr.reply)
{
GNUNET_break (0);
- TALER_EXCHANGE_disconnect (exchange);
- exchange = NULL;
test_shutdown ();
global_ret = EXIT_FAILURE;
return;
@@ -676,11 +671,9 @@ keys_cb (
default:
fprintf (stderr,
"Failed to download keys: %s (HTTP status: %u/%u)\n",
- hr->hint,
- hr->http_status,
- (unsigned int) hr->ec);
- TALER_EXCHANGE_disconnect (exchange);
- exchange = NULL;
+ kr->hr.hint,
+ kr->hr.http_status,
+ (unsigned int) kr->hr.ec);
test_shutdown ();
global_ret = EXIT_FAILURE;
return;
@@ -689,7 +682,7 @@ keys_cb (
GNUNET_JSON_pack_string ("operation",
OP_INPUT_KEYS),
GNUNET_JSON_pack_object_incref ("arguments",
- (json_t *) hr->reply));
+ (json_t *) kr->hr.reply));
if (NULL == args[0])
{
json_dumpf (in,
@@ -698,9 +691,8 @@ keys_cb (
json_decref (in);
in = NULL;
}
- TALER_EXCHANGE_disconnect (exchange);
- exchange = NULL;
next (args);
+ TALER_EXCHANGE_keys_decref (keys);
}
@@ -727,11 +719,11 @@ do_download (char *const *args)
global_ret = EXIT_NOTCONFIGURED;
return;
}
- exchange = TALER_EXCHANGE_connect (ctx,
- exchange_url,
- &keys_cb,
- (void *) args,
- TALER_EXCHANGE_OPTION_END);
+ exchange = TALER_EXCHANGE_get_keys (ctx,
+ exchange_url,
+ NULL,
+ &keys_cb,
+ (void *) args);
GNUNET_free (exchange_url);
}
@@ -742,58 +734,27 @@ do_download (char *const *args)
* @param denomkeys keys to output
* @return #GNUNET_OK on success
*/
-static int
+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 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_Amount fee_withdraw;
- struct TALER_Amount fee_deposit;
- struct TALER_Amount fee_refresh;
- struct TALER_Amount fee_refund;
- struct TALER_MasterSignatureP master_sig;
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_denom_pub ("denom_pub",
- &denom_pub),
- TALER_JSON_spec_amount ("value",
- currency,
- &coin_value),
- TALER_JSON_spec_amount ("fee_withdraw",
- currency,
- &fee_withdraw),
- TALER_JSON_spec_amount ("fee_deposit",
- currency,
- &fee_deposit),
- TALER_JSON_spec_amount ("fee_refresh",
- currency,
- &fee_refresh),
- TALER_JSON_spec_amount ("fee_refund",
- currency,
- &fee_refund),
- 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),
+ TALER_JSON_spec_denomination_group (NULL,
+ currency,
+ &group),
+ GNUNET_JSON_spec_array_const ("denoms",
+ &denoms),
GNUNET_JSON_spec_end ()
};
- struct GNUNET_TIME_Relative duration;
- struct TALER_DenominationHash h_denom_pub;
+ size_t index2;
+ json_t *value2;
if (GNUNET_OK !=
GNUNET_JSON_parse (value,
@@ -811,73 +772,116 @@ show_denomkeys (const json_t *denomkeys)
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,
- &coin_value,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund,
- &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;
- }
+ 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 (&fee_withdraw);
- deposit_fee_s = TALER_amount_to_string (&fee_deposit);
- refresh_fee_s = TALER_amount_to_string (&fee_refresh);
- refund_fee_s = TALER_amount_to_string (&fee_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 (&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);
+ {
+ 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;
@@ -971,11 +975,11 @@ do_show (char *const *args)
json_t *keys;
const char *err_name;
unsigned int err_line;
- json_t *denomkeys;
+ const json_t *denomkeys;
struct TALER_MasterPublicKeyP mpub;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("denoms",
- &denomkeys),
+ GNUNET_JSON_spec_array_const ("denominations",
+ &denomkeys),
GNUNET_JSON_spec_fixed_auto ("master_public_key",
&mpub),
GNUNET_JSON_spec_end ()
@@ -1019,11 +1023,9 @@ do_show (char *const *args)
{
global_ret = EXIT_FAILURE;
test_shutdown ();
- GNUNET_JSON_parse_free (spec);
json_decref (keys);
return;
}
- GNUNET_JSON_parse_free (spec);
json_decref (keys);
/* do NOT consume input if next argument is '-' */
if ( (NULL != args[0]) &&
@@ -1043,57 +1045,27 @@ do_show (char *const *args)
* @param denomkeys keys to output
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
sign_denomkeys (const json_t *denomkeys)
{
- size_t index;
+ size_t group_idx;
json_t *value;
- json_array_foreach (denomkeys, index, 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 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_Amount fee_withdraw;
- struct TALER_Amount fee_deposit;
- struct TALER_Amount fee_refresh;
- struct TALER_Amount fee_refund;
- struct TALER_MasterSignatureP master_sig;
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_denom_pub ("denom_pub",
- &denom_pub),
- TALER_JSON_spec_amount ("value",
- currency,
- &coin_value),
- TALER_JSON_spec_amount ("fee_withdraw",
- currency,
- &fee_withdraw),
- TALER_JSON_spec_amount ("fee_deposit",
- currency,
- &fee_deposit),
- TALER_JSON_spec_amount ("fee_refresh",
- currency,
- &fee_refresh),
- TALER_JSON_spec_amount ("fee_refund",
- currency,
- &fee_refund),
- 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),
+ TALER_JSON_spec_denomination_group (NULL,
+ currency,
+ &group),
+ GNUNET_JSON_spec_array_const ("denoms",
+ &denom_keys_array),
GNUNET_JSON_spec_end ()
};
- struct TALER_DenominationHash h_denom_pub;
+ size_t index;
+ json_t *denom_key_obj;
if (GNUNET_OK !=
GNUNET_JSON_parse (value,
@@ -1105,60 +1077,100 @@ sign_denomkeys (const json_t *denomkeys)
"Invalid input for denomination key to 'sign': %s#%u at %u (skipping)\n",
err_name,
err_line,
- (unsigned int) index);
+ (unsigned int) group_idx);
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,
- &coin_value,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund,
- &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;
- }
+ 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,
- &coin_value,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund,
- &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)));
+ {
+ 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);
}
@@ -1178,10 +1190,10 @@ do_sign (char *const *args)
const char *err_name;
unsigned int err_line;
struct TALER_MasterPublicKeyP mpub;
- json_t *denomkeys;
+ const json_t *denomkeys;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("denoms",
- &denomkeys),
+ GNUNET_JSON_spec_array_const ("denominations",
+ &denomkeys),
GNUNET_JSON_spec_fixed_auto ("master_public_key",
&mpub),
GNUNET_JSON_spec_end ()
@@ -1236,11 +1248,9 @@ do_sign (char *const *args)
{
global_ret = EXIT_FAILURE;
test_shutdown ();
- GNUNET_JSON_parse_free (spec);
json_decref (keys);
return;
}
- GNUNET_JSON_parse_free (spec);
json_decref (keys);
next (args);
}
diff --git a/src/exchange-tools/taler-crypto-worker.c b/src/exchange-tools/taler-crypto-worker.c
deleted file mode 100644
index 6690b912b..000000000
--- a/src/exchange-tools/taler-crypto-worker.c
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file util/taler-crypto-worker.c
- * @brief Standalone process to perform various cryptographic operations.
- * @author Florian Dold
- */
-#include "platform.h"
-#include "taler_util.h"
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_crypto_lib.h>
-#include "taler_error_codes.h"
-#include "taler_json_lib.h"
-#include "taler_signatures.h"
-
-
-/**
- * Return value from main().
- */
-static int global_ret;
-
-
-/**
- * Main function that will be run under the GNUnet scheduler.
- *
- * @param cls closure
- * @param args remaining command-line arguments
- * @param cfgfile name of the configuration file used (for saving, can be NULL!)
- * @param cfg configuration
- */
-static void
-run (void *cls,
- char *const *args,
- const char *cfgfile,
- const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- (void) cls;
- (void) args;
- (void) cfgfile;
-
- json_t *req;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "started crypto worker\n");
-
- for (;;)
- {
- const char *op;
- const json_t *args;
- req = json_loadf (stdin, JSON_DISABLE_EOF_CHECK, NULL);
- if (NULL == req)
- {
- if (feof (stdin))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "end of input\n");
- global_ret = 0;
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "invalid JSON\n");
- global_ret = 1;
- return;
- }
- op = json_string_value (json_object_get (req,
- "op"));
- if (! op)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "no op specified\n");
- global_ret = 1;
- return;
- }
- args = json_object_get (req, "args");
- if (! args)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "no args specified\n");
- global_ret = 1;
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "got request\n");
- if (0 == strcmp ("eddsa_verify",
- op))
- {
- struct GNUNET_CRYPTO_EddsaPublicKey pub;
- struct GNUNET_CRYPTO_EddsaSignature sig;
- struct GNUNET_CRYPTO_EccSignaturePurpose *msg;
- size_t msg_size;
- enum GNUNET_GenericReturnValue verify_ret;
- json_t *resp;
- struct GNUNET_JSON_Specification eddsa_verify_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("pub",
- &pub),
- GNUNET_JSON_spec_fixed_auto ("sig",
- &sig),
- GNUNET_JSON_spec_varsize ("msg",
- (void **) &msg,
- &msg_size),
- GNUNET_JSON_spec_end ()
- };
- if (GNUNET_OK != GNUNET_JSON_parse (args,
- eddsa_verify_spec,
- NULL,
- NULL))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "malformed op args\n");
- global_ret = 1;
- return;
- }
- verify_ret = GNUNET_CRYPTO_eddsa_verify_ (
- ntohl (msg->purpose),
- msg,
- &sig,
- &pub);
- resp = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_bool ("valid",
- GNUNET_OK == verify_ret));
- json_dumpf (resp, stdout, JSON_COMPACT);
- printf ("\n");
- fflush (stdout);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "sent response\n");
- continue;
- }
- if (0 == strcmp ("eddsa_sign",
- op))
- {
- struct GNUNET_CRYPTO_EddsaSignature sig;
- struct GNUNET_CRYPTO_EccSignaturePurpose *msg;
- struct GNUNET_CRYPTO_EddsaPrivateKey priv;
- size_t msg_size;
- json_t *resp;
- struct GNUNET_JSON_Specification eddsa_sign_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("priv",
- &priv),
- GNUNET_JSON_spec_varsize ("msg",
- (void **) &msg,
- &msg_size),
- GNUNET_JSON_spec_end ()
- };
- if (GNUNET_OK != GNUNET_JSON_parse (args,
- eddsa_sign_spec,
- NULL,
- NULL))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "malformed op args\n");
- global_ret = 1;
- return;
- }
- GNUNET_CRYPTO_eddsa_sign_ (
- &priv,
- msg,
- &sig
- );
- resp = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("sig", &sig)
- );
- json_dumpf (resp, stdout, JSON_COMPACT);
- printf ("\n");
- fflush (stdout);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "sent response\n");
- continue;
- }
- if (0 == strcmp ("setup_refresh_planchet", op))
- {
- struct TALER_TransferSecretP transfer_secret;
- uint32_t coin_index;
- json_t *resp;
- struct GNUNET_JSON_Specification setup_refresh_planchet_spec[] = {
- GNUNET_JSON_spec_uint32 ("coin_index",
- &coin_index),
- GNUNET_JSON_spec_fixed_auto ("transfer_secret",
- &transfer_secret),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_PlanchetSecretsP ps;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (args,
- setup_refresh_planchet_spec,
- NULL,
- NULL))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "malformed op args\n");
- global_ret = 1;
- return;
- }
- TALER_planchet_setup_refresh (&transfer_secret,
- coin_index,
- &ps);
- GNUNET_CRYPTO_eddsa_key_get_public (&ps.coin_priv.eddsa_priv,
- &coin_pub.eddsa_pub);
-
- resp = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("coin_priv", &ps.coin_priv),
- GNUNET_JSON_pack_data_auto ("coin_pub", &coin_pub),
- GNUNET_JSON_pack_data_auto ("blinding_key", &ps.blinding_key)
- );
- json_dumpf (resp, stdout, JSON_COMPACT);
- printf ("\n");
- fflush (stdout);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "sent response\n");
- continue;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "unsupported operation '%s'\n",
- op);
- global_ret = 1;
- return;
- }
-
-}
-
-
-/**
- * The entry point.
- *
- * @param argc number of arguments in @a argv
- * @param argv command-line arguments
- * @return 0 on normal termination
- */
-int
-main (int argc,
- char **argv)
-{
- struct GNUNET_GETOPT_CommandLineOption options[] = {
- GNUNET_GETOPT_OPTION_END
- };
- int ret;
-
- /* force linker to link against libtalerutil; if we do
- not do this, the linker may "optimize" libtalerutil
- away and skip #TALER_OS_init(), which we do need */
- TALER_OS_init ();
- ret = GNUNET_PROGRAM_run (argc, argv,
- "taler-crypto-worker",
- "Execute cryptographic operations read from stdin",
- options,
- &run,
- NULL);
- if (GNUNET_NO == ret)
- return 0;
- if (GNUNET_SYSERR == ret)
- return 1;
- return global_ret;
-}
diff --git a/src/exchange-tools/taler-exchange-dbinit.c b/src/exchange-tools/taler-exchange-dbinit.c
index a5e6a94af..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-2021 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
@@ -30,6 +30,11 @@
static int global_ret;
/**
+ * -a option: inject auditor triggers
+ */
+static int inject_auditor;
+
+/**
* -r option: do full DB reset
*/
static int reset_db;
@@ -44,6 +49,15 @@ static int clear_shards;
*/
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.
@@ -64,6 +78,7 @@ run (void *cls,
(void) cls;
(void) args;
(void) cfgfile;
+
if (NULL ==
(plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
{
@@ -74,14 +89,17 @@ run (void *cls,
}
if (reset_db)
{
- if (GNUNET_OK != plugin->drop_tables (plugin->cls))
+ if (GNUNET_OK !=
+ plugin->drop_tables (plugin->cls))
{
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");
@@ -104,7 +122,7 @@ run (void *cls,
}
if (clear_shards)
{
- if (0 >
+ if (GNUNET_OK !=
plugin->delete_shard_locks (plugin->cls))
{
fprintf (stderr,
@@ -120,6 +138,15 @@ run (void *cls,
}
}
}
+ if (inject_auditor)
+ {
+ if (GNUNET_SYSERR == plugin->inject_auditor_triggers (plugin->cls))
+ {
+ fprintf (stderr,
+ "Injecting auditor triggers failed!\n");
+ global_ret = EXIT_FAILURE;
+ }
+ }
TALER_EXCHANGEDB_plugin_unload (plugin);
plugin = NULL;
}
@@ -138,6 +165,10 @@ main (int argc,
char *const *argv)
{
const struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_flag ('a',
+ "inject-auditor",
+ "inject auditor triggers",
+ &inject_auditor),
GNUNET_GETOPT_option_flag ('g',
"gc",
"garbage collect database",
@@ -150,6 +181,15 @@ main (int argc,
"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;
diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c
index 9255737a9..1f10c55e3 100644
--- a/src/exchange-tools/taler-exchange-offline.c
+++ b/src/exchange-tools/taler-exchange-offline.c
@@ -1,18 +1,18 @@
/*
- This file is part of TALER
- Copyright (C) 2020, 2021 Taler Systems SA
+ 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 free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
- TALER is distributed in the hope that it will be useful, but WITHOUT 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 taler-exchange-offline.c
* @brief Support for operations involving the exchange's offline master key.
@@ -20,8 +20,12 @@
*/
#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.
@@ -66,6 +70,13 @@
#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.
@@ -93,6 +104,25 @@
*/
#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().
@@ -120,6 +150,12 @@ static struct GNUNET_CURL_RescheduleContext *rc;
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;
@@ -145,7 +181,6 @@ static char *currency;
*/
static char *CFG_exchange_url;
-
/**
* A subcommand supported by this program.
*/
@@ -367,6 +402,62 @@ struct WireFeeRequest
/**
+ * 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
@@ -392,6 +483,88 @@ struct UploadKeysRequest
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.
@@ -403,6 +576,27 @@ static struct GNUNET_SCHEDULER_Task *nxt;
*/
static struct TALER_EXCHANGE_ManagementGetKeysHandle *mgkh;
+
+/**
+ * Active AML staff change requests.
+ */
+static struct AmlStaffRequest *asr_head;
+
+/**
+ * Active AML staff change requests.
+ */
+static struct AmlStaffRequest *asr_tail;
+
+/**
+ * Active partner add requests.
+ */
+static struct PartnerAddRequest *par_head;
+
+/**
+ * Active partner add requests.
+ */
+static struct PartnerAddRequest *par_tail;
+
/**
* Active denomiantion revocation requests.
*/
@@ -474,6 +668,16 @@ static struct WireFeeRequest *wfr_head;
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;
@@ -483,6 +687,26 @@ static struct UploadKeysRequest *ukr_head;
*/
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.
@@ -495,6 +719,36 @@ do_shutdown (void *cls)
(void) cls;
{
+ struct AmlStaffRequest *asr;
+
+ while (NULL != (asr = asr_head))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Aborting incomplete AML staff update #%u\n",
+ (unsigned int) asr->idx);
+ TALER_EXCHANGE_management_update_aml_officer_cancel (asr->h);
+ GNUNET_CONTAINER_DLL_remove (asr_head,
+ asr_tail,
+ asr);
+ GNUNET_free (asr);
+ }
+ }
+ {
+ struct PartnerAddRequest *par;
+
+ while (NULL != (par = par_head))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Aborting incomplete partner add request #%u\n",
+ (unsigned int) par->idx);
+ TALER_EXCHANGE_management_add_partner_cancel (par->h);
+ GNUNET_CONTAINER_DLL_remove (par_head,
+ par_tail,
+ par);
+ GNUNET_free (par);
+ }
+ }
+ {
struct DenomRevocationRequest *drr;
while (NULL != (drr = drr_head))
@@ -601,6 +855,21 @@ do_shutdown (void *cls)
}
}
{
+ 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))
@@ -615,11 +884,44 @@ do_shutdown (void *cls)
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)
{
- json_dumpf (out,
- stdout,
- JSON_INDENT (2));
+ if (EXIT_SUCCESS == global_ret)
+ json_dumpf (out,
+ stdout,
+ JSON_INDENT (2));
json_decref (out);
out = NULL;
}
@@ -660,13 +962,18 @@ 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 ();
@@ -803,6 +1110,9 @@ load_offline_key (int do_create)
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;
}
@@ -812,14 +1122,15 @@ load_offline_key (int do_create)
* Function called with information about the post revocation operation result.
*
* @param cls closure with a `struct DenomRevocationRequest`
- * @param hr HTTP response data
+ * @param dr response data
*/
static void
denom_revocation_cb (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+ 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)
{
@@ -852,7 +1163,7 @@ upload_denom_revocation (const char *exchange_url,
const json_t *value)
{
struct TALER_MasterSignatureP master_sig;
- struct TALER_DenominationHash h_denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
struct DenomRevocationRequest *drr;
const char *err_name;
unsigned int err_line;
@@ -879,7 +1190,7 @@ upload_denom_revocation (const char *exchange_url,
stderr,
JSON_INDENT (2));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return;
}
drr = GNUNET_new (struct DenomRevocationRequest);
@@ -901,14 +1212,15 @@ upload_denom_revocation (const char *exchange_url,
* Function called with information about the post revocation operation result.
*
* @param cls closure with a `struct SignkeyRevocationRequest`
- * @param hr HTTP response data
+ * @param sr response data
*/
static void
signkey_revocation_cb (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+ 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)
{
@@ -968,7 +1280,7 @@ upload_signkey_revocation (const char *exchange_url,
stderr,
JSON_INDENT (2));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return;
}
srr = GNUNET_new (struct SignkeyRevocationRequest);
@@ -990,13 +1302,15 @@ upload_signkey_revocation (const char *exchange_url,
* Function called with information about the post auditor add operation result.
*
* @param cls closure with a `struct AuditorAddRequest`
- * @param hr HTTP response data
+ * @param mer response data
*/
static void
-auditor_add_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+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)
{
@@ -1037,7 +1351,7 @@ upload_auditor_add (const char *exchange_url,
const char *err_name;
unsigned int err_line;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("auditor_url",
+ TALER_JSON_spec_web_url ("auditor_url",
&auditor_url),
GNUNET_JSON_spec_string ("auditor_name",
&auditor_name),
@@ -1065,7 +1379,7 @@ upload_auditor_add (const char *exchange_url,
stderr,
JSON_INDENT (2));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return;
}
aar = GNUNET_new (struct AuditorAddRequest);
@@ -1090,13 +1404,15 @@ upload_auditor_add (const char *exchange_url,
* Function called with information about the post auditor del operation result.
*
* @param cls closure with a `struct AuditorDelRequest`
- * @param hr HTTP response data
+ * @param mdr response data
*/
static void
auditor_del_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+ 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)
{
@@ -1159,7 +1475,7 @@ upload_auditor_del (const char *exchange_url,
stderr,
JSON_INDENT (2));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return;
}
adr = GNUNET_new (struct AuditorDelRequest);
@@ -1182,13 +1498,14 @@ upload_auditor_del (const char *exchange_url,
* Function called with information about the post wire add operation result.
*
* @param cls closure with a `struct WireAddRequest`
- * @param hr HTTP response data
+ * @param wer response data
*/
static void
wire_add_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+ 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)
{
@@ -1226,10 +1543,29 @@ upload_wire_add (const char *exchange_url,
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[] = {
- GNUNET_JSON_spec_string ("payto_uri",
- &payto_uri),
+ 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",
@@ -1254,7 +1590,7 @@ upload_wire_add (const char *exchange_url,
stderr,
JSON_INDENT (2));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return;
}
{
@@ -1267,20 +1603,39 @@ upload_wire_add (const char *exchange_url,
"payto:// URI `%s' is malformed\n",
payto_uri);
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ 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,
@@ -1293,13 +1648,14 @@ upload_wire_add (const char *exchange_url,
* Function called with information about the post wire del operation result.
*
* @param cls closure with a `struct WireDelRequest`
- * @param hr HTTP response data
+ * @param wdres response data
*/
static void
wire_del_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+ 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)
{
@@ -1338,8 +1694,8 @@ upload_wire_del (const char *exchange_url,
const char *err_name;
unsigned int err_line;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("payto_uri",
- &payto_uri),
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &payto_uri),
GNUNET_JSON_spec_timestamp ("validity_end",
&end_time),
GNUNET_JSON_spec_fixed_auto ("master_sig",
@@ -1362,7 +1718,7 @@ upload_wire_del (const char *exchange_url,
stderr,
JSON_INDENT (2));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return;
}
wdr = GNUNET_new (struct WireDelRequest);
@@ -1385,14 +1741,15 @@ upload_wire_del (const char *exchange_url,
* Function called with information about the post wire fee operation result.
*
* @param cls closure with a `struct WireFeeRequest`
- * @param hr HTTP response data
+ * @param swr response data
*/
static void
wire_fee_cb (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+ 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)
{
@@ -1429,8 +1786,7 @@ upload_wire_fee (const char *exchange_url,
struct WireFeeRequest *wfr;
const char *err_name;
unsigned int err_line;
- struct TALER_Amount wire_fee;
- struct TALER_Amount closing_fee;
+ struct TALER_WireFeeSet fees;
struct GNUNET_TIME_Timestamp start_time;
struct GNUNET_TIME_Timestamp end_time;
struct GNUNET_JSON_Specification spec[] = {
@@ -1438,10 +1794,10 @@ upload_wire_fee (const char *exchange_url,
&wire_method),
TALER_JSON_spec_amount ("wire_fee",
currency,
- &wire_fee),
+ &fees.wire),
TALER_JSON_spec_amount ("closing_fee",
currency,
- &closing_fee),
+ &fees.closing),
GNUNET_JSON_spec_timestamp ("start_time",
&start_time),
GNUNET_JSON_spec_timestamp ("end_time",
@@ -1466,7 +1822,7 @@ upload_wire_fee (const char *exchange_url,
stderr,
JSON_INDENT (2));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return;
}
wfr = GNUNET_new (struct WireFeeRequest);
@@ -1477,8 +1833,7 @@ upload_wire_fee (const char *exchange_url,
wire_method,
start_time,
end_time,
- &wire_fee,
- &closing_fee,
+ &fees,
&master_sig,
&wire_fee_cb,
wfr);
@@ -1489,17 +1844,242 @@ upload_wire_fee (const char *exchange_url,
/**
+ * 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 hr HTTP response data
+ * @param mr response data
*/
static void
keys_cb (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+ 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)
{
@@ -1535,13 +2115,13 @@ upload_keys (const char *exchange_url,
struct UploadKeysRequest *ukr;
const char *err_name;
unsigned int err_line;
- json_t *denom_sigs;
- json_t *signkey_sigs;
+ const json_t *denom_sigs;
+ const json_t *signkey_sigs;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("denom_sigs",
- &denom_sigs),
- GNUNET_JSON_spec_json ("signkey_sigs",
- &signkey_sigs),
+ 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;
@@ -1560,7 +2140,7 @@ upload_keys (const char *exchange_url,
stderr,
JSON_INDENT (2));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return;
}
pkd.num_sign_sigs = json_array_size (signkey_sigs);
@@ -1653,11 +2233,358 @@ upload_keys (const char *exchange_url,
else
{
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
}
GNUNET_free (pkd.sign_sigs);
GNUNET_free (pkd.denom_sigs);
- GNUNET_JSON_parse_free (spec);
+}
+
+
+/**
+ * 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);
}
@@ -1699,9 +2626,29 @@ trigger_upload (const char *exchange_url)
.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
@@ -1722,7 +2669,7 @@ trigger_upload (const char *exchange_url)
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Malformed JSON input\n");
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return;
}
/* block of code that uses key and value */
@@ -1731,6 +2678,7 @@ trigger_upload (const char *exchange_url)
if (0 == strcasecmp (key,
uhs[i].key))
{
+
found = true;
uhs[i].cb (exchange_url,
index,
@@ -1744,7 +2692,7 @@ trigger_upload (const char *exchange_url)
"Upload does not know how to handle `%s'\n",
key);
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return;
}
}
@@ -1764,7 +2712,7 @@ do_upload (char *const *args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Downloaded data was not consumed, refusing upload\n");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_FAILURE;
return;
}
@@ -1783,7 +2731,7 @@ do_upload (char *const *args)
err.line,
err.source,
err.position);
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_FAILURE;
return;
}
@@ -1792,7 +2740,7 @@ do_upload (char *const *args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Error: expected JSON array for `upload` command\n");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_FAILURE;
return;
}
@@ -1807,7 +2755,7 @@ do_upload (char *const *args)
"exchange",
"BASE_URL");
global_ret = EXIT_NOTCONFIGURED;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return;
}
trigger_upload (CFG_exchange_url);
@@ -1825,14 +2773,14 @@ do_upload (char *const *args)
static void
do_revoke_denomination_key (char *const *args)
{
- struct TALER_DenominationHash h_denom_pub;
+ 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");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_FAILURE;
return;
}
@@ -1845,7 +2793,7 @@ do_revoke_denomination_key (char *const *args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"You must specify a denomination key with this subcommand\n");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
@@ -1881,7 +2829,7 @@ do_revoke_signkey (char *const *args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Downloaded data was not consumed, refusing revocation\n");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_FAILURE;
return;
}
@@ -1894,7 +2842,7 @@ do_revoke_signkey (char *const *args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"You must specify an exchange signing key with this subcommand\n");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
@@ -1932,7 +2880,7 @@ do_add_auditor (char *const *args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Downloaded data was not consumed, not adding auditor\n");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_FAILURE;
return;
}
@@ -1945,7 +2893,7 @@ do_add_auditor (char *const *args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"You must specify an auditor public key as first argument for this subcommand\n");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
@@ -1958,7 +2906,7 @@ do_add_auditor (char *const *args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"You must specify an auditor URI and auditor name as 2nd and 3rd arguments to this subcommand\n");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
@@ -2004,7 +2952,7 @@ do_del_auditor (char *const *args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Downloaded data was not consumed, not deleting auditor account\n");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_FAILURE;
return;
}
@@ -2017,7 +2965,7 @@ do_del_auditor (char *const *args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"You must specify an auditor public key as first argument for this subcommand\n");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
@@ -2042,6 +2990,96 @@ do_del_auditor (char *const *args)
/**
+ * 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;
@@ -2053,12 +3091,18 @@ 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");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_FAILURE;
return;
}
@@ -2066,13 +3110,27 @@ do_add_wire (char *const *args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"You must specify a payto://-URI with this subcommand\n");
- test_shutdown ();
+ 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;
@@ -2084,29 +3142,160 @@ do_add_wire (char *const *args)
"payto:// URI `%s' is malformed\n",
args[0]);
global_ret = EXIT_INVALIDARGUMENT;
- test_shutdown ();
+ 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 + 1);
+ next (args + num_args);
}
@@ -2126,7 +3315,7 @@ do_del_wire (char *const *args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Downloaded data was not consumed, not deleting wire account\n");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_FAILURE;
return;
}
@@ -2134,7 +3323,7 @@ do_del_wire (char *const *args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"You must specify a payto://-URI with this subcommand\n");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
@@ -2162,7 +3351,7 @@ do_del_wire (char *const *args)
* 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 fee and args[2]
+ * args[0] must be the year, args[1] the wire method, args[2] the wire fee and args[3]
* the closing fee.
*/
static void
@@ -2171,8 +3360,7 @@ do_set_wire_fee (char *const *args)
struct TALER_MasterSignatureP master_sig;
char dummy;
unsigned int year;
- struct TALER_Amount wire_fee;
- struct TALER_Amount closing_fee;
+ struct TALER_WireFeeSet fees;
struct GNUNET_TIME_Timestamp start_time;
struct GNUNET_TIME_Timestamp end_time;
@@ -2180,7 +3368,7 @@ do_set_wire_fee (char *const *args)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Downloaded data was not consumed, not setting wire fee\n");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_FAILURE;
return;
}
@@ -2196,14 +3384,14 @@ do_set_wire_fee (char *const *args)
args[0])) ) ||
(GNUNET_OK !=
TALER_string_to_amount (args[2],
- &wire_fee)) ||
+ &fees.wire)) ||
(GNUNET_OK !=
TALER_string_to_amount (args[3],
- &closing_fee)) )
+ &fees.closing)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "You must use YEAR, METHOD, WIRE-FEE and CLOSING-FEE as arguments for this subcommand\n");
- test_shutdown ();
+ "You must use YEAR, METHOD, WIRE-FEE, and CLOSING-FEE as arguments for this subcommand\n");
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_INVALIDARGUMENT;
return;
}
@@ -2221,8 +3409,7 @@ do_set_wire_fee (char *const *args)
TALER_exchange_offline_wire_fee_sign (args[1],
start_time,
end_time,
- &wire_fee,
- &closing_fee,
+ &fees,
&master_priv,
&master_sig);
output_operation (OP_SET_WIRE_FEE,
@@ -2234,9 +3421,9 @@ do_set_wire_fee (char *const *args)
GNUNET_JSON_pack_timestamp ("end_time",
end_time),
TALER_JSON_pack_amount ("wire_fee",
- &wire_fee),
+ &fees.wire),
TALER_JSON_pack_amount ("closing_fee",
- &closing_fee),
+ &fees.closing),
GNUNET_JSON_pack_data_auto ("master_sig",
&master_sig)));
next (args + 4);
@@ -2244,23 +3431,522 @@ do_set_wire_fee (char *const *args)
/**
+ * 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 hr HTTP response data
- * @param keys information about the various keys used
- * by the exchange, NULL if /management/keys failed
+ * @param mgr response data
*/
static void
download_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_EXCHANGE_FutureKeys *keys)
+ const struct TALER_EXCHANGE_ManagementGetKeysResponse *mgr)
{
char *const *args = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &mgr->hr;
- (void) keys;
mgkh = NULL;
switch (hr->http_status)
{
@@ -2278,7 +3964,7 @@ download_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to download keys from `%s' (no HTTP response)\n",
CFG_exchange_url);
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_FAILURE;
return;
}
@@ -2317,7 +4003,7 @@ do_download (char *const *args)
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchange",
"BASE_URL");
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
global_ret = EXIT_NOTCONFIGURED;
return;
}
@@ -2332,16 +4018,16 @@ do_download (char *const *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 secm security module keys, must be an array of length 2
+ * @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 int
-tofu_check (const struct TALER_SecurityModulePublicKeyP secm[2])
+static enum GNUNET_GenericReturnValue
+tofu_check (const struct TALER_SecurityModulePublicKeySetP *secmset)
{
char *fn;
- struct TALER_SecurityModulePublicKeyP old[2];
+ struct TALER_SecurityModulePublicKeySetP oldset;
ssize_t ret;
if (GNUNET_OK !=
@@ -2359,11 +4045,11 @@ tofu_check (const struct TALER_SecurityModulePublicKeyP secm[2])
GNUNET_DISK_file_test (fn))
{
ret = GNUNET_DISK_fn_read (fn,
- &old,
- sizeof (old));
+ &oldset,
+ sizeof (oldset));
if (GNUNET_SYSERR != ret)
{
- if (ret != sizeof (old))
+ if (ret != sizeof (oldset))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"File `%s' corrupt\n",
@@ -2372,9 +4058,9 @@ tofu_check (const struct TALER_SecurityModulePublicKeyP secm[2])
return GNUNET_SYSERR;
}
/* TOFU check */
- if (0 != memcmp (old,
- secm,
- sizeof (old)))
+ if (0 != memcmp (&oldset,
+ secmset,
+ sizeof (*secmset)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Fatal: security module keys changed (file `%s')!\n",
@@ -2410,15 +4096,17 @@ tofu_check (const struct TALER_SecurityModulePublicKeyP secm[2])
"SECM_ESIGN_PUBKEY",
"key malformed");
GNUNET_free (key);
+ GNUNET_free (fn);
return GNUNET_SYSERR;
}
GNUNET_free (key);
if (0 !=
GNUNET_memcmp (&k,
- &secm[1]))
+ &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;
}
}
@@ -2441,15 +4129,50 @@ tofu_check (const struct TALER_SecurityModulePublicKeyP secm[2])
"SECM_DENOM_PUBKEY",
"key malformed");
GNUNET_free (key);
+ GNUNET_free (fn);
return GNUNET_SYSERR;
}
GNUNET_free (key);
if (0 !=
GNUNET_memcmp (&k,
- &secm[0]))
+ &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;
}
}
@@ -2466,8 +4189,8 @@ tofu_check (const struct TALER_SecurityModulePublicKeyP secm[2])
/* persist keys for future runs */
if (GNUNET_OK !=
GNUNET_DISK_fn_write (fn,
- secm,
- sizeof (old),
+ secmset,
+ sizeof (oldset),
GNUNET_DISK_PERM_USER_READ))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -2476,6 +4199,7 @@ tofu_check (const struct TALER_SecurityModulePublicKeyP secm[2])
GNUNET_free (fn);
return GNUNET_SYSERR;
}
+ GNUNET_free (fn);
return GNUNET_NO;
}
@@ -2487,7 +4211,7 @@ tofu_check (const struct TALER_SecurityModulePublicKeyP secm[2])
* @param signkeys keys to output
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
show_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
const json_t *signkeys)
{
@@ -2532,7 +4256,7 @@ show_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
stderr,
JSON_INDENT (2));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return GNUNET_SYSERR;
}
duration = GNUNET_TIME_absolute_get_difference (start_time.abs_time,
@@ -2548,7 +4272,7 @@ show_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
"Invalid security module signature for signing key %s (aborting)\n",
TALER_B2S (&exchange_pub));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return GNUNET_SYSERR;
}
{
@@ -2572,12 +4296,14 @@ show_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
/**
* Output @a denomkeys for human consumption.
*
- * @param secm_pub security module public key used to sign the denominations
+ * @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 int
-show_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
+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;
@@ -2593,10 +4319,7 @@ show_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
struct GNUNET_TIME_Timestamp stamp_expire_deposit;
struct GNUNET_TIME_Timestamp stamp_expire_legal;
struct TALER_Amount coin_value;
- struct TALER_Amount fee_withdraw;
- struct TALER_Amount fee_deposit;
- struct TALER_Amount fee_refresh;
- struct TALER_Amount fee_refund;
+ struct TALER_DenomFeeSet fees;
struct TALER_SecurityModuleSignatureP secm_sig;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("section_name",
@@ -2606,18 +4329,9 @@ show_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
TALER_JSON_spec_amount ("value",
currency,
&coin_value),
- TALER_JSON_spec_amount ("fee_withdraw",
- currency,
- &fee_withdraw),
- TALER_JSON_spec_amount ("fee_deposit",
- currency,
- &fee_deposit),
- TALER_JSON_spec_amount ("fee_refresh",
- currency,
- &fee_refresh),
- TALER_JSON_spec_amount ("fee_refund",
- currency,
- &fee_refund),
+ TALER_JSON_SPEC_DENOM_FEES ("fee",
+ currency,
+ &fees),
GNUNET_JSON_spec_timestamp ("stamp_start",
&stamp_start),
GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
@@ -2631,7 +4345,7 @@ show_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
GNUNET_JSON_spec_end ()
};
struct GNUNET_TIME_Relative duration;
- struct TALER_DenominationHash h_denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
enum GNUNET_GenericReturnValue ok;
if (GNUNET_OK !=
@@ -2650,7 +4364,7 @@ show_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
JSON_INDENT (2));
GNUNET_JSON_parse_free (spec);
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return GNUNET_SYSERR;
}
duration = GNUNET_TIME_absolute_get_difference (
@@ -2658,22 +4372,36 @@ show_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
stamp_expire_withdraw.abs_time);
TALER_denom_pub_hash (&denom_pub,
&h_denom_pub);
- switch (denom_pub.cipher)
+ switch (denom_pub.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
{
struct TALER_RsaPubHashP h_rsa;
- TALER_rsa_pub_hash (denom_pub.details.rsa_public_key,
+ 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,
+ 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;
@@ -2685,7 +4413,7 @@ show_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
"Invalid security module signature for denomination key %s (aborting)\n",
GNUNET_h2s (&h_denom_pub.hash));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return GNUNET_SYSERR;
}
@@ -2697,10 +4425,10 @@ show_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
char *deposit_s;
char *legal_s;
- withdraw_fee_s = TALER_amount_to_string (&fee_withdraw);
- deposit_fee_s = TALER_amount_to_string (&fee_deposit);
- refresh_fee_s = TALER_amount_to_string (&fee_refresh);
- refund_fee_s = TALER_amount_to_string (&fee_refund);
+ 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 (
@@ -2772,7 +4500,7 @@ parse_keys_input (const char *command_name)
err.source,
err.position);
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return NULL;
}
}
@@ -2791,7 +4519,7 @@ parse_keys_input (const char *command_name)
stderr,
JSON_INDENT (2));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return NULL;
}
if (0 != strcmp (op_str,
@@ -2822,27 +4550,30 @@ do_show (char *const *args)
json_t *keys;
const char *err_name;
unsigned int err_line;
- json_t *denomkeys;
- json_t *signkeys;
+ const json_t *denomkeys;
+ const json_t *signkeys;
struct TALER_MasterPublicKeyP mpub;
- struct TALER_SecurityModulePublicKeyP secm[2];
+ struct TALER_SecurityModulePublicKeySetP secmset;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("future_denoms",
- &denomkeys),
- GNUNET_JSON_spec_json ("future_signkeys",
- &signkeys),
+ 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",
- &secm[0]),
+ &secmset.rsa),
+ GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key",
+ &secmset.cs),
GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key",
- &secm[1]),
+ &secmset.eddsa),
GNUNET_JSON_spec_end ()
};
keys = parse_keys_input ("show");
if (NULL == keys)
return;
+
if (GNUNET_OK !=
load_offline_key (GNUNET_NO))
return;
@@ -2860,7 +4591,7 @@ do_show (char *const *args)
stderr,
JSON_INDENT (2));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
json_decref (keys);
return;
}
@@ -2871,35 +4602,32 @@ do_show (char *const *args)
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Fatal: exchange uses different master key!\n");
global_ret = EXIT_FAILURE;
- test_shutdown ();
- GNUNET_JSON_parse_free (spec);
+ GNUNET_SCHEDULER_shutdown ();
json_decref (keys);
return;
}
if (GNUNET_SYSERR ==
- tofu_check (secm))
+ tofu_check (&secmset))
{
global_ret = EXIT_FAILURE;
- test_shutdown ();
- GNUNET_JSON_parse_free (spec);
+ GNUNET_SCHEDULER_shutdown ();
json_decref (keys);
return;
}
if ( (GNUNET_OK !=
- show_signkeys (&secm[1],
+ show_signkeys (&secmset.eddsa,
signkeys)) ||
(GNUNET_OK !=
- show_denomkeys (&secm[0],
+ show_denomkeys (&secmset.rsa,
+ &secmset.cs,
denomkeys)) )
{
global_ret = EXIT_FAILURE;
- test_shutdown ();
- GNUNET_JSON_parse_free (spec);
+ GNUNET_SCHEDULER_shutdown ();
json_decref (keys);
return;
}
json_decref (keys);
- GNUNET_JSON_parse_free (spec);
next (args);
}
@@ -2912,7 +4640,7 @@ do_show (char *const *args)
* @param[in,out] result array where to output the signatures
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
sign_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
const json_t *signkeys,
json_t *result)
@@ -2958,7 +4686,7 @@ sign_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
stderr,
JSON_INDENT (2));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return GNUNET_SYSERR;
}
@@ -2975,7 +4703,7 @@ sign_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
"Invalid security module signature for signing key %s (aborting)\n",
TALER_B2S (&exchange_pub));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
@@ -3004,15 +4732,54 @@ sign_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
/**
+ * 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 security module public key used to sign the denominations
+ * @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,
+sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub_rsa,
+ const struct TALER_SecurityModulePublicKeyP *secm_pub_cs,
const json_t *denomkeys,
json_t *result)
{
@@ -3029,10 +4796,7 @@ sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
struct GNUNET_TIME_Timestamp stamp_expire_deposit;
struct GNUNET_TIME_Timestamp stamp_expire_legal;
struct TALER_Amount coin_value;
- struct TALER_Amount fee_withdraw;
- struct TALER_Amount fee_deposit;
- struct TALER_Amount fee_refresh;
- struct TALER_Amount fee_refund;
+ struct TALER_DenomFeeSet fees;
struct TALER_SecurityModuleSignatureP secm_sig;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("section_name",
@@ -3042,18 +4806,9 @@ sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
TALER_JSON_spec_amount ("value",
currency,
&coin_value),
- TALER_JSON_spec_amount ("fee_withdraw",
- currency,
- &fee_withdraw),
- TALER_JSON_spec_amount ("fee_deposit",
- currency,
- &fee_deposit),
- TALER_JSON_spec_amount ("fee_refresh",
- currency,
- &fee_refresh),
- TALER_JSON_spec_amount ("fee_refund",
- currency,
- &fee_refund),
+ TALER_JSON_SPEC_DENOM_FEES ("fee",
+ currency,
+ &fees),
GNUNET_JSON_spec_timestamp ("stamp_start",
&stamp_start),
GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
@@ -3067,7 +4822,7 @@ sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
GNUNET_JSON_spec_end ()
};
struct GNUNET_TIME_Relative duration;
- struct TALER_DenominationHash h_denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
if (GNUNET_OK !=
GNUNET_JSON_parse (value,
@@ -3085,36 +4840,64 @@ sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
JSON_INDENT (2));
GNUNET_JSON_parse_free (spec);
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
return GNUNET_SYSERR;
}
duration = GNUNET_TIME_absolute_get_difference (
stamp_start.abs_time,
stamp_expire_withdraw.abs_time);
- // FIXME-Oec: setup age mask here?
+
+ /* 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.cipher)
+
+ switch (denom_pub.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
{
struct TALER_RsaPubHashP h_rsa;
- TALER_rsa_pub_hash (denom_pub.details.rsa_public_key,
+ 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,
+ 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;
- test_shutdown ();
+ 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;
}
@@ -3122,7 +4905,7 @@ sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
break;
default:
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
@@ -3136,10 +4919,7 @@ sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
stamp_expire_deposit,
stamp_expire_legal,
&coin_value,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund,
+ &fees,
&master_priv,
&master_sig);
GNUNET_assert (0 ==
@@ -3168,21 +4948,23 @@ do_sign (char *const *args)
json_t *keys;
const char *err_name;
unsigned int err_line;
- json_t *denomkeys;
- json_t *signkeys;
+ const json_t *denomkeys;
+ const json_t *signkeys;
struct TALER_MasterPublicKeyP mpub;
- struct TALER_SecurityModulePublicKeyP secm[2];
+ struct TALER_SecurityModulePublicKeySetP secmset;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("future_denoms",
- &denomkeys),
- GNUNET_JSON_spec_json ("future_signkeys",
- &signkeys),
+ 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",
- &secm[0]),
+ &secmset.rsa),
+ GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key",
+ &secmset.cs),
GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key",
- &secm[1]),
+ &secmset.eddsa),
GNUNET_JSON_spec_end ()
};
@@ -3209,7 +4991,7 @@ do_sign (char *const *args)
stderr,
JSON_INDENT (2));
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
json_decref (keys);
return;
}
@@ -3220,19 +5002,17 @@ do_sign (char *const *args)
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Fatal: exchange uses different master key!\n");
global_ret = EXIT_FAILURE;
- test_shutdown ();
- GNUNET_JSON_parse_free (spec);
+ GNUNET_SCHEDULER_shutdown ();
json_decref (keys);
return;
}
if (GNUNET_SYSERR ==
- tofu_check (secm))
+ tofu_check (&secmset))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Fatal: security module keys changed!\n");
global_ret = EXIT_FAILURE;
- test_shutdown ();
- GNUNET_JSON_parse_free (spec);
+ GNUNET_SCHEDULER_shutdown ();
json_decref (keys);
return;
}
@@ -3243,19 +5023,19 @@ do_sign (char *const *args)
GNUNET_assert (NULL != signkey_sig_array);
GNUNET_assert (NULL != denomkey_sig_array);
if ( (GNUNET_OK !=
- sign_signkeys (&secm[1],
+ sign_signkeys (&secmset.eddsa,
signkeys,
signkey_sig_array)) ||
(GNUNET_OK !=
- sign_denomkeys (&secm[0],
+ sign_denomkeys (&secmset.rsa,
+ &secmset.cs,
denomkeys,
denomkey_sig_array)) )
{
global_ret = EXIT_FAILURE;
- test_shutdown ();
+ GNUNET_SCHEDULER_shutdown ();
json_decref (signkey_sig_array);
json_decref (denomkey_sig_array);
- GNUNET_JSON_parse_free (spec);
json_decref (keys);
return;
}
@@ -3267,7 +5047,6 @@ do_sign (char *const *args)
GNUNET_JSON_pack_array_steal ("signkey_sigs",
signkey_sig_array)));
}
- GNUNET_JSON_parse_free (spec);
json_decref (keys);
next (args);
}
@@ -3314,6 +5093,188 @@ do_setup (char *const *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)
{
@@ -3369,7 +5330,7 @@ work (void *cls)
{
.name = "enable-account",
.help =
- "enable wire account of the exchange (payto-URI must be given as argument)",
+ "enable wire account of the exchange (payto-URI must be given as argument; for optional arguments see man page)",
.cb = &do_add_wire
},
{
@@ -3381,15 +5342,50 @@ work (void *cls)
{
.name = "wire-fee",
.help =
- "sign wire fees for the given year (year, wire method, wire fee and closing fee must be given as arguments)",
+ "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,
@@ -3397,34 +5393,7 @@ work (void *cls)
};
(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]))
- {
- 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);
- }
+ cmd_handler (args, cmds);
}
@@ -3445,6 +5414,24 @@ run (void *cls,
(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))
@@ -3452,6 +5439,7 @@ run (void *cls,
global_ret = EXIT_NOTCONFIGURED;
return;
}
+
ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
&rc);
rc = GNUNET_CURL_gnunet_rc_create (ctx);
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 e7688f735..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,6 +32,7 @@ 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 \
@@ -50,9 +56,35 @@ taler_exchange_closer_LDADD = \
-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 \
+ $(top_builddir)/src/bank-lib/libtalerbank.la \
+ $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+ -ljansson \
+ -lgnunetcurl \
+ -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 \
@@ -76,23 +108,54 @@ taler_exchange_transfer_LDADD = \
-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_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 \
@@ -101,16 +164,26 @@ taler_exchange_httpd_SOURCES = \
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_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) \
@@ -118,7 +191,10 @@ taler_exchange_httpd_LDADD = \
$(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 \
@@ -139,7 +215,6 @@ check_SCRIPTS += \
test_taler_exchange_httpd_afl.sh
endif
-.NOTPARALLEL:
TESTS = \
$(check_SCRIPTS)
@@ -152,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 92de5e31c..ce471a292 100644
--- a/src/exchange/exchange.conf
+++ b/src/exchange/exchange.conf
@@ -6,6 +6,33 @@
# 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.
@@ -15,7 +42,7 @@ MAX_KEYS_CACHING = forever
# After how many requests should the exchange auto-restart
# (to address potential issues with memory fragmentation)?
# If this option is not specified, auto-restarting is disabled.
-# MAX_REQUESTS = 10000000
+# MAX_REQUESTS = 100000
# How to access our database
DB = postgres
@@ -40,13 +67,26 @@ PORT = 8081
# transfers to enable tracking.
BASE_URL = http://localhost:8081/
-# Maximum number of requests this process should handle before
-# committing suicide.
-# MAX_REQUESTS =
-
# How long should the aggregator sleep if it has nothing to do?
AGGREGATOR_IDLE_SLEEP_INTERVAL = 60 s
+# 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?
TRANSFER_IDLE_SLEEP_INTERVAL = 60 s
@@ -66,6 +106,17 @@ CLOSER_IDLE_SLEEP_INTERVAL = 60 s
# 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.)
@@ -75,42 +126,13 @@ WIREWATCH_IDLE_SLEEP_INTERVAL = 1 s
SIGNKEY_LEGAL_DURATION = 2 years
# Directory with our terms of service.
-TERMS_DIR = $DATADIR/exchange/tos/
+TERMS_DIR = $TALER_DATA_HOME/terms/
# Etag / filename for the terms of service.
-TERMS_ETAG = 0
+TERMS_ETAG = exchange-tos-v0
# Directory with our privacy policy.
-PRIVACY_DIR = $DATADIR/exchange/pp/
+PRIVACY_DIR = $TALER_DATA_HOME/terms/
# Etag / filename for the privacy policy.
-PRIVACY_ETAG = 0
-
-# Set to NONE to disable KYC checks.
-# Set to "OAUTH2" to use OAuth 2.0 for KYC authorization.
-KYC_MODE = NONE
-
-# Balance threshold above which wallets are told
-# to undergo a KYC check at the exchange. Optional,
-# if not given there is no limit.
-# KYC_WALLET_BALANCE_LIMIT = CURRENCY:150
-#
-# KYC_WITHDRAW_PERIOD = 1 month
-
-[exchange-kyc-oauth2]
-
-# URL of the OAuth endpoint for KYC checks
-# KYC_OAUTH2_URL =
-
-# URL of the "information" endpoint for KYC checks
-# KYC_INFO_URL =
-
-# KYC Oauth client ID.
-# KYC_OAUTH2_CLIENT_ID =
-
-# KYC Client secret used to obtain access tokens.
-# KYC_OAUTH2_CLIENT_SECRET =
-
-# Where to redirect clients after successful
-# authorization?
-# KYC_OAUTH2_POST_URL = https://bank.com/
+PRIVACY_ETAG = exchange-pp-v0
diff --git a/src/exchange/taler-exchange-aggregator.c b/src/exchange/taler-exchange-aggregator.c
index 32b069f90..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-2021 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,7 +63,7 @@ struct AggregationUnit
/**
* Wire fee we charge for @e wp at @e execution_time.
*/
- struct TALER_Amount wire_fee;
+ struct TALER_WireFeeSet fees;
/**
* Wire transfer identifier we use.
@@ -63,11 +71,6 @@ 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).
*/
@@ -81,7 +84,7 @@ struct AggregationUnit
/**
* Selected wire target for the aggregation.
*/
- uint64_t wire_target;
+ struct TALER_PaytoHashP h_payto;
/**
* Exchange wire account to be used for the preparation and
@@ -90,20 +93,24 @@ struct AggregationUnit
const struct TALER_EXCHANGEDB_AccountInfo *wa;
/**
- * Array of row_ids from the aggregation.
+ * Row in KYC table for legitimization requirements
+ * that are pending for this aggregation, or 0 if none.
*/
- uint64_t additional_rows[TALER_EXCHANGEDB_MATCHING_DEPOSITS_LIMIT];
+ uint64_t requirement_row;
/**
- * Offset specifying how many @e additional_rows are in use.
+ * Set to #GNUNET_OK during transient checking
+ * while everything is OK. Otherwise see return
+ * value of #do_aggregate().
*/
- unsigned int rows_offset;
+ enum GNUNET_GenericReturnValue ret;
/**
- * Set to true if we encountered a refund during #refund_by_coin_cb.
- * Used to wave the deposit fee.
+ * Do we have an entry in the transient table for
+ * this aggregation?
*/
- bool have_refund;
+ bool have_transient;
+
};
@@ -143,6 +150,12 @@ struct Shard
static struct TALER_Amount currency_round_unit;
/**
+ * What is the largest amount we transfer before triggering
+ * an AML check?
+ */
+static struct TALER_Amount aml_threshold;
+
+/**
* What is the base URL of this exchange? Used in the
* wire transfer subjects so that merchants and governments
* can ask for the list of aggregated deposits.
@@ -171,7 +184,6 @@ static struct TALER_EXCHANGEDB_Plugin *db_plugin;
*/
static struct GNUNET_SCHEDULER_Task *task;
-
/**
* How long should we sleep when idle before trying to find more work?
*/
@@ -199,19 +211,19 @@ 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);
/**
- * Select a shard to work on.
+ * Work on transactions unlocked by KYC.
*
* @param cls NULL
*/
static void
-run_shard (void *cls);
+drain_kyc_alerts (void *cls);
/**
@@ -246,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 ();
@@ -254,12 +267,12 @@ shutdown_task (void *cls)
/**
- * Parse the configuration for wirewatch.
+ * Parse the configuration for aggregator.
*
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
-parse_wirewatch_config (void)
+parse_aggregator_config (void)
{
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
@@ -288,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,
+ "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 non-zero value in section `TALER' under `CURRENCY_ROUND_UNIT'\n");
+ "Need amount in section `exchange' under `AML_THRESHOLD'\n");
return GNUNET_SYSERR;
}
@@ -318,28 +340,235 @@ 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.
+ *
+ * @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 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
+ * @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);
+ 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;
+ }
+}
+
+
+/**
+ * Trigger the wire transfer for the @a au_active
+ * and delete the record of the aggregation.
+ *
+ * @param au_active information about the aggregation
+ */
+static enum GNUNET_DB_QueryStatus
+trigger_wire_transfer (const struct AggregationUnit *au_active)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Preparing wire transfer of %s to %s\n",
+ TALER_amount2s (&au_active->final_amount),
+ TALER_B2S (&au_active->merchant_pub));
+ {
+ void *buf;
+ size_t buf_size;
+
+ 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;
+}
+
+
+/**
+ * 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_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to select aggregation amounts for KYC limit check!\n");
+ }
+}
+
+
+/**
+ * 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 (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return false;
+ }
+ if (NULL == requirement)
+ return true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "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)
+ {
+ 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;
+}
+
+
+/**
+ * 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
-refund_by_coin_cb (void *cls,
- const struct TALER_Amount *amount_with_fee)
+sum_for_aml (
+ void *cls,
+ const struct TALER_Amount *amount,
+ struct GNUNET_TIME_Absolute date)
{
- struct AggregationUnit *aux = cls;
+ struct TALER_Amount *sum = cls;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Aggregator subtracts applicable refund of amount %s\n",
- TALER_amount2s (amount_with_fee));
- aux->have_refund = true;
+ (void) date;
if (0 >
- TALER_amount_subtract (&aux->total_amount,
- &aux->total_amount,
- amount_with_fee))
+ TALER_amount_add (sum,
+ sum,
+ amount))
{
GNUNET_break (0);
return GNUNET_SYSERR;
@@ -349,117 +578,117 @@ refund_by_coin_cb (void *cls,
/**
- * Function called with details about deposits that have been made,
- * with the goal of executing the corresponding wire transaction.
+ * Test if AML is required for a transfer to @a h_payto.
*
- * @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_target target account for the wire transfer
- * @param payto_uri URI of the target account
- * @return transaction status code, #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT to continue to iterate
+ * @param[in,out] au_active aggregation unit to check for
+ * @return true if AML checks are satisfied
*/
-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 TALER_PrivateContractHash *h_contract_terms,
- uint64_t wire_target,
- const char *payto_uri)
+static bool
+aml_satisfied (struct AggregationUnit *au_active)
{
- struct AggregationUnit *au = cls;
enum GNUNET_DB_QueryStatus qs;
+ struct TALER_Amount total;
+ struct TALER_Amount threshold;
+ enum TALER_AmlDecisionState decision;
+ struct TALER_EXCHANGEDB_KycStatus kyc;
- 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 = false;
- qs = db_plugin->select_refunds_by_coin (db_plugin->cls,
- coin_pub,
- &au->merchant_pub,
- h_contract_terms,
- &refund_by_coin_cb,
- au);
- if (0 > qs)
+ 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;
}
- if (! au->have_refund)
+ qs = db_plugin->select_aml_threshold (db_plugin->cls,
+ &au_active->h_payto,
+ &decision,
+ &kyc,
+ &threshold);
+ if (qs < 0)
{
- struct TALER_Amount ntotal;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Non-refunded transaction, subtracting deposit fee %s\n",
- TALER_amount2s (deposit_fee));
- if (0 >
- TALER_amount_subtract (&ntotal,
- amount_with_fee,
- deposit_fee))
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return false;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ threshold = aml_threshold; /* use default */
+ decision = TALER_AML_NORMAL;
+ }
+ switch (decision)
+ {
+ case TALER_AML_NORMAL:
+ if (0 >= TALER_amount_cmp (&total,
+ &threshold))
{
- /* 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_set_zero (au->total_amount.currency,
- &au->total_amount));
+ /* total <= threshold, do nothing */
+ return true;
}
- else
+ qs = db_plugin->trigger_aml_process (db_plugin->cls,
+ &au_active->h_payto,
+ &total);
+ if (qs < 0)
{
- au->total_amount = ntotal;
+ 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;
+}
- GNUNET_assert (NULL == au->payto_uri);
- au->payto_uri = GNUNET_strdup (payto_uri);
- au->wire_target = wire_target;
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
- &au->wtid,
- sizeof (au->wtid));
- 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);
- au->wa = TALER_EXCHANGEDB_find_account_by_payto_uri (payto_uri);
+
+/**
+ * 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[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_GenericReturnValue
+do_aggregate (struct AggregationUnit *au)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ 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",
- payto_uri);
- return GNUNET_DB_STATUS_HARD_ERROR;
+ au->payto_uri);
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
}
- /* make sure we have current fees */
- au->execution_time = GNUNET_TIME_timestamp_get ();
{
- struct TALER_Amount closing_fee;
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,
au->wa->method,
au->execution_time,
&start_date,
&end_date,
- &au->wire_fee,
- &closing_fee,
+ &au->fees,
&master_sig);
if (0 >= qs)
{
@@ -467,234 +696,178 @@ deposit_cb (void *cls,
"Could not get wire fees for %s at %s. Aborting run.\n",
au->wa->method,
GNUNET_TIME_timestamp2s (au->execution_time));
- return GNUNET_DB_STATUS_HARD_ERROR;
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
}
}
+ /* Now try to find other deposits to aggregate */
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->wtid,
- row_id);
- if (qs <= 0)
+ "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;
}
- 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,
- row_id);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ 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)
{
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to execute aggregation!\n");
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
}
- return qs;
-}
-
-
-/**
- * Function called with details about another deposit we
- * can aggregate into an existing aggregation unit.
- *
- * @param cls a `struct AggregationUnit`
- * @param row_id identifies database entry
- * @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
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-aggregate_cb (void *cls,
- uint64_t row_id,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct TALER_PrivateContractHash *h_contract_terms)
-{
- struct AggregationUnit *au = cls;
- struct TALER_Amount old;
- enum GNUNET_DB_QueryStatus qs;
-
- if (au->rows_offset >= TALER_EXCHANGEDB_MATCHING_DEPOSITS_LIMIT)
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
- /* 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;
+ /* serializiability issue, try again */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "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));
- /* add to total */
- 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 = false;
- qs = db_plugin->select_refunds_by_coin (db_plugin->cls,
- coin_pub,
- &au->merchant_pub,
- h_contract_terms,
- &refund_by_coin_cb,
- au);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
- if (! au->have_refund)
- {
- struct TALER_Amount tmp;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Subtracting deposit fee %s for non-refunded coin\n",
- TALER_amount2s (deposit_fee));
- if (0 >
- TALER_amount_subtract (&tmp,
+ 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))
- {
- 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_set_zero (old.currency,
- &au->total_amount));
- }
+ &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)
{
- au->total_amount = tmp;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization issue, trying again later!\n");
+ return GNUNET_NO;
}
- }
-
- /* now add the au->total_amount with the (remaining) contribution of
- the current coin to the 'old' value with the current aggregate value */
- {
- struct TALER_Amount tmp;
-
- if (0 >
- TALER_amount_add (&tmp,
- &au->total_amount,
- &old))
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
- 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;
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
}
- au->total_amount = tmp;
- }
-
- /* "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->wtid,
- row_id);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
- qs = db_plugin->mark_deposit_done (db_plugin->cls,
- row_id);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
+ /* commit */
+ return GNUNET_OK;
}
- 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.
- *
- * @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,
- "aggregator",
- s->shard_start,
- s->shard_end);
- GNUNET_free (s);
+ qs = trigger_wire_transfer (au);
switch (qs)
{
- case GNUNET_DB_STATUS_HARD_ERROR:
case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ 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);
- 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 */
+ global_ret = EXIT_FAILURE;
+ return GNUNET_SYSERR;
+ default:
break;
}
+ {
+ 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;
+
}
-/**
- * Main work function that queries the DB and aggregates transactions
- * into larger wire transfers.
- *
- * @param cls a `struct Shard *`
- */
static void
run_aggregation (void *cls)
{
struct Shard *s = cls;
struct AggregationUnit au_active;
enum GNUNET_DB_QueryStatus qs;
+ enum GNUNET_GenericReturnValue ret;
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))
{
@@ -705,16 +878,12 @@ run_aggregation (void *cls)
release_shard (s);
return;
}
- memset (&au_active,
- 0,
- sizeof (au_active));
qs = db_plugin->get_ready_deposit (
db_plugin->cls,
s->shard_start,
s->shard_end,
- kyc_off ? true : false,
- &deposit_cb,
- &au_active);
+ &au_active.merchant_pub,
+ &au_active.payto_uri);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -753,202 +922,63 @@ run_aggregation (void *cls)
(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,
- &run_shard,
+ &drain_kyc_alerts,
NULL);
+ }
else
- task = GNUNET_SCHEDULER_add_now (&run_shard,
+ {
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
+ }
return;
}
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
s->work_counter++;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found ready deposit!\n");
/* continued below */
break;
}
- /* Now try to find other deposits to aggregate */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Found ready deposit for %s, aggregating by target %llu\n",
- TALER_B2S (&au_active.merchant_pub),
- (unsigned long long) au_active.wire_target);
- qs = db_plugin->iterate_matching_deposits (db_plugin->cls,
- au_active.wire_target,
- &au_active.merchant_pub,
- &aggregate_cb,
- &au_active,
- TALER_EXCHANGEDB_MATCHING_DEPOSITS_LIMIT);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ TALER_payto_hash (au_active.payto_uri,
+ &au_active.h_payto);
+ ret = do_aggregate (&au_active);
+ cleanup_au (&au_active);
+ switch (ret)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to execute deposit iteration!\n");
- cleanup_au (&au_active);
- db_plugin->rollback (db_plugin->cls);
+ case GNUNET_SYSERR:
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
+ db_plugin->rollback (db_plugin->cls);
release_shard (s);
return;
- }
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- /* serializiability issue, try again */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Serialization issue, trying again later!\n");
+ case GNUNET_NO:
db_plugin->rollback (db_plugin->cls);
- cleanup_au (&au_active);
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_aggregation,
s);
return;
+ case GNUNET_OK:
+ /* continued below */
+ break;
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Found %d other deposits to combine into wire transfer.\n",
- qs);
-
- /* 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 ( (0 >=
- 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);
- /* 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,
- "aggregator mark tiny transactions"))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to start database transaction!\n");
- global_ret = EXIT_FAILURE;
- cleanup_au (&au_active);
- GNUNET_SCHEDULER_shutdown ();
- release_shard (s);
- return;
- }
- /* Mark transactions by row_id as minor */
- qs = db_plugin->mark_deposit_tiny (db_plugin->cls,
- 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,
- au_active.additional_rows[i]);
- if (0 > qs)
- break;
- }
- }
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Serialization issue, trying again later!\n");
- db_plugin->rollback (db_plugin->cls);
- cleanup_au (&au_active);
- /* start again */
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
- s);
- return;
- }
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- db_plugin->rollback (db_plugin->cls);
- cleanup_au (&au_active);
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- release_shard (s);
- return;
- }
- /* commit */
- (void) commit_or_warn ();
- cleanup_au (&au_active);
-
- /* start again */
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
- s);
- return;
- }
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Preparing wire transfer of %s to %s\n",
- TALER_amount2s (&au_active.final_amount),
- TALER_B2S (&au_active.merchant_pub));
- {
- void *buf;
- size_t buf_size;
-
- 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' to finally satisfy aggregation
- table constraints */
- if (qs >= 0)
- qs = db_plugin->store_wire_transfer_out (db_plugin->cls,
- au_active.execution_time,
- &au_active.wtid,
- au_active.wire_target,
- 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);
- /* start again */
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
- s);
- return;
- }
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- GNUNET_break (0);
- db_plugin->rollback (db_plugin->cls);
- /* die hard */
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- release_shard (s);
- return;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Stored wire transfer out instructions\n");
+ "Committing aggregation result\n");
/* Now we can finally commit the overall transaction, as we are
again consistent if all of this passes. */
@@ -956,8 +986,8 @@ run_aggregation (void *cls)
{
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,
s);
@@ -966,11 +996,12 @@ run_aggregation (void *cls)
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,
- "Preparation complete, going again\n");
+ "Commit complete, going again\n");
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_aggregation,
s);
@@ -979,6 +1010,7 @@ run_aggregation (void *cls)
GNUNET_break (0);
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
+ db_plugin->rollback (db_plugin->cls); /* just in case */
release_shard (s);
return;
}
@@ -998,6 +1030,8 @@ run_shard (void *cls)
(void) cls;
task = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running aggregation shard\n");
if (GNUNET_SYSERR ==
db_plugin->preflight (db_plugin->cls))
{
@@ -1024,6 +1058,7 @@ run_shard (void *cls)
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);
@@ -1041,12 +1076,207 @@ run_shard (void *cls)
"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);
}
/**
+ * 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 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
+drain_kyc_alerts (void *cls)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct AggregationUnit au;
+
+ (void) cls;
+ task = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "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 connection!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ db_plugin->start (db_plugin->cls,
+ "handle kyc alerts"))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start database transaction!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ while (1)
+ {
+ qs = db_plugin->drain_kyc_alert (db_plugin->cls,
+ 1,
+ &au.h_payto);
+ switch (qs)
+ {
+ 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;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
+ NULL);
+ return;
+ 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_now (&run_shard,
+ NULL);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* handled below */
+ break;
+ }
+
+ 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 lookup transient aggregates!\n");
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
+ NULL);
+ return;
+ 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);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
+ NULL);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ continue; /* while (1) */
+ default:
+ break;
+ }
+ break;
+ } /* while(1) */
+
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = au.ret;
+ cleanup_au (&au);
+ switch (ret)
+ {
+ 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;
+ }
+ }
+
+ switch (commit_or_warn ())
+ {
+ 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 (&drain_kyc_alerts,
+ NULL);
+ 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 */
+ 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 (&drain_kyc_alerts,
+ NULL);
+ return;
+ default:
+ GNUNET_break (0);
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ db_plugin->rollback (db_plugin->cls); /* just in case */
+ return;
+ }
+}
+
+
+/**
* First task.
*
* @param cls closure, NULL
@@ -1066,7 +1296,8 @@ run (void *cls,
(void) cfgfile;
cfg = c;
- if (GNUNET_OK != parse_wirewatch_config ())
+ if (GNUNET_OK !=
+ parse_aggregator_config ())
{
cfg = NULL;
global_ret = EXIT_NOTCONFIGURED;
@@ -1087,11 +1318,18 @@ run (void *cls,
shard_size = 1U + INT32_MAX;
else
shard_size = (uint32_t) ass;
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_init (cfg))
+ {
+ cfg = NULL;
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ NULL);
GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_shard,
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
- GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
- cls);
}
diff --git a/src/exchange/taler-exchange-closer.c b/src/exchange/taler-exchange-closer.c
index 3847e05b2..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-2021 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
@@ -204,19 +204,25 @@ commit_or_warn (void)
* @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_Timestamp expiration_date)
+ struct GNUNET_TIME_Timestamp expiration_date,
+ uint64_t close_request_row)
{
struct GNUNET_TIME_Timestamp now;
struct TALER_WireTransferIdentifierRawP wtid;
struct TALER_Amount amount_without_fee;
struct TALER_Amount closing_fee;
+ struct TALER_WireFeeSet fees;
enum TALER_AmountArithmeticResult ret;
enum GNUNET_DB_QueryStatus qs;
const struct TALER_EXCHANGEDB_AccountInfo *wa;
@@ -238,13 +244,12 @@ expired_reserve_cb (void *cls,
account_payto_uri);
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_Amount wire_fee;
struct GNUNET_TIME_Timestamp start_date;
struct GNUNET_TIME_Timestamp end_date;
struct TALER_MasterSignatureP master_sig;
@@ -255,20 +260,29 @@ expired_reserve_cb (void *cls,
expiration_date,
&start_date,
&end_date,
- &wire_fee,
- &closing_fee,
+ &fees,
&master_sig);
- if (0 >= qs)
+ switch (qs)
{
+ 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_DB_STATUS_HARD_ERROR;
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return GNUNET_NO;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* continued below */
+ break;
}
}
/* calculate transfer amount */
+ closing_fee = fees.closing;
ret = TALER_amount_subtract (&amount_without_fee,
left,
&closing_fee);
@@ -281,8 +295,8 @@ expired_reserve_cb (void *cls,
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (left->currency,
&amount_without_fee));
+ ret = TALER_AAR_RESULT_ZERO;
}
- GNUNET_assert (TALER_AAR_RESULT_POSITIVE == ret);
/* round down to enable transfer */
if (GNUNET_SYSERR ==
TALER_amount_round_down (&amount_without_fee,
@@ -291,27 +305,25 @@ expired_reserve_cb (void *cls,
GNUNET_break (0);
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
- return GNUNET_DB_STATUS_HARD_ERROR;
+ return GNUNET_SYSERR;
}
/* 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 (TALER_AAR_INVALID_NEGATIVE_RESULT != ret)
- qs = db_plugin->insert_reserve_closed (db_plugin->cls,
- 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),
@@ -319,22 +331,30 @@ expired_reserve_cb (void *cls,
(int) ret,
qs);
/* Check for hard failure */
- if ( (TALER_AAR_INVALID_NEGATIVE_RESULT == ret) ||
- (GNUNET_DB_STATUS_HARD_ERROR == qs) )
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
- return GNUNET_DB_STATUS_HARD_ERROR;
+ return GNUNET_SYSERR;
}
- if ( (TALER_AAR_RESULT_ZERO == ret) ||
- (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) )
+ if (TALER_amount_is_zero (&amount_without_fee))
{
/* Reserve balance was zero OR soft error */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Reserve was virtually empty, moving on\n");
- (void) commit_or_warn ();
- 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 */
@@ -355,19 +375,25 @@ expired_reserve_cb (void *cls,
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 = 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,11 +435,18 @@ run_reserve_closures (void *cls)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Checking for reserves to close by date %s\n",
GNUNET_TIME_timestamp2s (now));
- qs = db_plugin->get_expired_reserves (db_plugin->cls,
- now,
- &expired_reserve_cb,
- NULL);
- GNUNET_assert (1 >= qs);
+ 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:
@@ -436,13 +469,11 @@ run_reserve_closures (void *cls)
if (GNUNET_YES == test_mode)
{
GNUNET_SCHEDULER_shutdown ();
+ return;
}
- else
- {
- task = GNUNET_SCHEDULER_add_delayed (closer_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 ();
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 59398c6fc..36459fbd7 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 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,32 +25,52 @@
#include <jansson.h>
#include <microhttpd.h>
#include <sched.h>
-#include <pthread.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_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_deposit.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_wire.h"
-#include "taler-exchange-httpd_withdraw.h"
#include "taler_exchangedb_lib.h"
#include "taler_exchangedb_plugin.h"
#include "taler_extensions.h"
@@ -62,6 +82,11 @@
#define UNIX_BACKLOG 50
/**
+ * How often will we try to connect to the database before giving up?
+ */
+#define MAX_DB_RETRIES 5
+
+/**
* Above what request latency do we start to log?
*/
#define WARN_LATENCY GNUNET_TIME_relative_multiply ( \
@@ -86,14 +111,17 @@ static int allow_address_reuse;
const struct GNUNET_CONFIGURATION_Handle *TEH_cfg;
/**
- * Handle to the HTTP server.
+ * Configuration of age restriction
+ *
+ * Set after loading the library, enabled in database event handler.
*/
-static struct MHD_Daemon *mhd;
+bool TEH_age_restriction_enabled = false;
+struct TALER_AgeRestrictionConfig TEH_age_restriction_config = {0};
/**
- * Our KYC configuration.
+ * Handle to the HTTP server.
*/
-struct TEH_KycOptions TEH_kyc_config;
+static struct MHD_Daemon *mhd;
/**
* How long is caching /keys allowed at most? (global)
@@ -112,16 +140,58 @@ struct GNUNET_TIME_Relative TEH_reserve_closing_delay;
struct TALER_MasterPublicKeyP TEH_master_public_key;
/**
+ * Key used to encrypt KYC attribute data in our database.
+ */
+struct TALER_AttributeEncryptionKeyP TEH_attribute_key;
+
+/**
* Our DB plugin. (global)
*/
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;
@@ -148,9 +218,12 @@ int TEH_check_invariants_flag;
bool TEH_suicide;
/**
- * Global register of extensions
+ * Signature of the configuration of all enabled extensions,
+ * signed by the exchange's offline master key with purpose
+ * TALER_SIGNATURE_MASTER_EXTENSION.
*/
-struct TALER_Extension **TEH_extensions;
+struct TALER_MasterSignatureP TEH_extensions_sig;
+bool TEH_extensions_signed = false;
/**
* Value to return from main()
@@ -180,12 +253,28 @@ static unsigned long long active_connections;
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 #exchange_curl_ctx with the
+ * Context for integrating #TEH_curl_ctx with the
* GNUnet event loop.
*/
static struct GNUNET_CURL_RescheduleContext *exchange_curl_rc;
@@ -228,8 +317,7 @@ r404 (struct MHD_Connection *connection,
*
* @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 MHD_RESULT
@@ -252,10 +340,6 @@ handle_post_coins (struct TEH_RequestContext *rc,
} h[] = {
{
- .op = "deposit",
- .handler = &TEH_handler_deposit
- },
- {
.op = "melt",
.handler = &TEH_handler_melt
},
@@ -301,6 +385,572 @@ handle_post_coins (struct TEH_RequestContext *rc,
/**
+ * 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_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.
*/
@@ -371,6 +1021,11 @@ handle_mhd_completion_callback (void *cls,
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
@@ -435,9 +1090,8 @@ proceed_with_handler (struct TEH_RequestContext *rc,
size_t *upload_data_size)
{
const struct TEH_RequestHandler *rh = rc->rh;
- const char *args[rh->nargs + 1];
+ const char *args[rh->nargs + 2];
size_t ulen = strlen (url) + 1;
- json_t *root = NULL;
MHD_RESULT ret;
/* We do check for "ulen" here, because we'll later stack-allocate a buffer
@@ -458,8 +1112,9 @@ proceed_with_handler (struct TEH_RequestContext *rc,
/* 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) )
{
enum GNUNET_GenericReturnValue res;
@@ -467,83 +1122,74 @@ proceed_with_handler (struct TEH_RequestContext *rc,
&rc->opaque_post_parsing_context,
upload_data,
upload_data_size,
- &root);
+ &rc->root);
if (GNUNET_SYSERR == res)
{
- GNUNET_assert (NULL == root);
+ GNUNET_assert (NULL == rc->root);
return MHD_NO; /* bad upload, could not even generate error */
}
if ( (GNUNET_NO == res) ||
- (NULL == root) )
+ (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 */
- args[0] = NULL;
- 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 ( (! rh->nargs_is_upper_bound) &&
- ( (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);
- json_decref (root);
- 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
- (even if handlers requested precise number of arguments) */
- args[i] = NULL;
+ 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 != root)
+ if (0 == strcasecmp (rh->method,
+ MHD_HTTP_METHOD_POST))
ret = rh->handler.post (rc,
- root,
+ 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) */
+ 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);
}
- json_decref (root);
return ret;
}
@@ -586,6 +1232,20 @@ handler_seed (struct TEH_RequestContext *rc,
/**
+ * 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
@@ -598,6 +1258,55 @@ 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);
@@ -638,7 +1347,7 @@ handle_post_management (struct TEH_RequestContext *rc,
if (0 == strcmp (args[0],
"denominations"))
{
- struct TALER_DenominationHash h_denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
if ( (NULL == args[0]) ||
(NULL == args[1]) ||
@@ -693,46 +1402,22 @@ handle_post_management (struct TEH_RequestContext *rc,
&exchange_pub,
root);
}
- if (0 == strcmp (args[0],
- "keys"))
+ for (unsigned int i = 0;
+ NULL != plain_posts[i].handler;
+ i++)
{
- if (NULL != args[1])
+ if (0 == strcmp (args[0],
+ plain_posts[i].arg0))
{
- GNUNET_break_op (0);
- return r404 (rc->connection,
- "/management/keys/*");
+ 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);
}
- return TEH_handler_management_post_keys (rc->connection,
- root);
- }
- if (0 == strcmp (args[0],
- "wire"))
- {
- if (NULL == args[1])
- return TEH_handler_management_post_wire (rc->connection,
- root);
- if ( (0 != strcmp (args[1],
- "disable")) ||
- (NULL != args[2]) )
- {
- GNUNET_break_op (0);
- return r404 (rc->connection,
- "/management/wire/disable");
- }
- return TEH_handler_management_post_wire_disable (rc->connection,
- root);
- }
- if (0 == strcmp (args[0],
- "wire-fee"))
- {
- if (NULL != args[1])
- {
- GNUNET_break_op (0);
- return r404 (rc->connection,
- "/management/wire-fee/*");
- }
- return TEH_handler_management_post_wire_fees (rc->connection,
- root);
}
GNUNET_break_op (0);
return r404 (rc->connection,
@@ -741,7 +1426,7 @@ handle_post_management (struct TEH_RequestContext *rc,
/**
- * Handle a get "/management" request.
+ * Handle a GET "/management" request.
*
* @param rc request context
* @param args array of additional options (must be [0] == "keys")
@@ -779,7 +1464,7 @@ handle_post_auditors (struct TEH_RequestContext *rc,
const char *const args[])
{
struct TALER_AuditorPublicKeyP auditor_pub;
- struct TALER_DenominationHash h_denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
if ( (NULL == args[0]) ||
(NULL == args[1]) ||
@@ -822,6 +1507,56 @@ handle_post_auditors (struct TEH_RequestContext *rc,
/**
+ * 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)
@@ -854,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
@@ -878,6 +1609,12 @@ handle_mhd_request (void *cls,
.method = MHD_HTTP_METHOD_GET,
.handler.get = &handler_seed
},
+ /* Configuration */
+ {
+ .url = "config",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &TEH_handler_config
+ },
/* Performance metrics */
{
.url = "metrics",
@@ -902,25 +1639,57 @@ handle_mhd_request (void *cls,
.method = MHD_HTTP_METHOD_GET,
.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",
@@ -931,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 */
{
@@ -955,12 +1725,40 @@ 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 = 1
+ .nargs = 3
},
{
.url = "kyc-proof",
@@ -974,6 +1772,20 @@ handle_mhd_request (void *cls,
.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",
@@ -997,6 +1809,28 @@ handle_mhd_request (void *cls,
.nargs = 4,
.nargs_is_upper_bound = true
},
+ /* AML endpoints */
+ {
+ .url = "aml",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler.get = &handle_get_aml,
+ .nargs = 4,
+ .nargs_is_upper_bound = true
+ },
+ {
+ .url = "aml",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler.post = &handle_post_aml,
+ .nargs = 2
+ },
+ {
+ .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
@@ -1037,30 +1871,8 @@ handle_mhd_request (void *cls,
if (0 == strcasecmp (method,
MHD_HTTP_METHOD_POST))
{
- const char *cl;
-
- /* 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)
- {
- unsigned long long cv;
- char dummy;
-
- if (1 != sscanf (cl,
- "%llu%c",
- &cv,
- &dummy))
- {
- /* Not valid HTTP request, just close connection. */
- GNUNET_break_op (0);
- return MHD_NO;
- }
- if (cv > TALER_MHD_REQUEST_BUFFER_MAX)
- return TALER_MHD_reply_request_too_large (connection);
- }
+ TALER_MHD_check_content_length (connection,
+ TALER_MHD_REQUEST_BUFFER_MAX);
}
}
@@ -1140,7 +1952,8 @@ handle_mhd_request (void *cls,
continue;
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);
@@ -1238,248 +2051,178 @@ handle_mhd_request (void *cls,
/**
- * Load general KYC configuration parameters for the exchange server into the
- * #TEH_kyc_config variable.
+ * Load configuration parameters for the exchange
+ * server into the corresponding global variables.
*
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
-parse_kyc_settings (void)
+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 !=
- GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
- "exchange",
- "KYC_WITHDRAW_PERIOD",
- &TEH_kyc_config.withdraw_period))
+ TALER_KYCLOGIC_kyc_init (TEH_cfg))
{
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "KYC_WITHDRAW_PERIOD",
- "valid relative time expected");
return GNUNET_SYSERR;
}
- if (GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period))
- return GNUNET_OK;
if (GNUNET_OK !=
- TALER_config_get_amount (TEH_cfg,
- "exchange",
- "KYC_WITHDRAW_LIMIT",
- &TEH_kyc_config.withdraw_limit))
- return GNUNET_SYSERR;
- if (0 != strcasecmp (TEH_kyc_config.withdraw_limit.currency,
- TEH_currency))
+ GNUNET_CONFIGURATION_get_value_number (TEH_cfg,
+ "exchange",
+ "MAX_REQUESTS",
+ &req_max))
{
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "KYC_WITHDRAW_LIMIT",
- "currency mismatch");
- return GNUNET_SYSERR;
+ req_max = ULLONG_MAX;
}
- return GNUNET_OK;
-}
-
-
-/**
- * Load OAuth2.0 configuration parameters for the exchange server into the
- * #TEH_kyc_config variable.
- *
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_kyc_oauth_cfg (void)
-{
- char *s;
-
if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_URL",
- &s))
+ GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+ "exchangedb",
+ "IDLE_RESERVE_EXPIRATION_TIME",
+ &TEH_reserve_closing_delay))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_URL");
- return GNUNET_SYSERR;
- }
- 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,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_URL",
- "not a valid URL");
- GNUNET_free (s);
- return GNUNET_SYSERR;
+ "exchangedb",
+ "IDLE_RESERVE_EXPIRATION_TIME");
+ /* use default */
+ TEH_reserve_closing_delay
+ = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_WEEKS,
+ 4);
}
- TEH_kyc_config.details.oauth2.url = s;
if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- "exchange-kyc-oauth2",
- "KYC_INFO_URL",
- &s))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_INFO_URL");
- return GNUNET_SYSERR;
- }
- if ( (! TALER_url_valid_charset (s)) ||
- ( (0 != strncasecmp (s,
- "http://",
- strlen ("http://"))) &&
- (0 != strncasecmp (s,
- "https://",
- strlen ("https://"))) ) )
+ GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+ "exchange",
+ "MAX_KEYS_CACHING",
+ &TEH_max_keys_caching))
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_INFO_URL",
- "not a valid URL");
- GNUNET_free (s);
+ "exchange",
+ "MAX_KEYS_CACHING",
+ "valid relative time expected");
return GNUNET_SYSERR;
}
- TEH_kyc_config.details.oauth2.info_url = s;
-
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_CLIENT_ID",
- &s))
+ "exchange",
+ "KYC_AML_TRIGGER",
+ &TEH_kyc_aml_trigger))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_CLIENT_ID");
+ "exchange",
+ "KYC_AML_TRIGGER");
return GNUNET_SYSERR;
}
- TEH_kyc_config.details.oauth2.client_id = s;
-
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_CLIENT_SECRET",
- &s))
+ "exchange",
+ "TOPLEVEL_REDIRECT_URL",
+ &toplevel_redirect_url))
{
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_CLIENT_SECRET");
- return GNUNET_SYSERR;
+ toplevel_redirect_url = GNUNET_strdup ("/terms");
}
- TEH_kyc_config.details.oauth2.client_secret = s;
-
if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_POST_URL",
- &s))
+ TALER_config_get_currency (TEH_cfg,
+ &TEH_currency))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange-kyc-oauth2",
- "KYC_OAUTH2_POST_URL");
+ "taler",
+ "CURRENCY");
return GNUNET_SYSERR;
}
- TEH_kyc_config.details.oauth2.post_kyc_redirect_url = s;
- return GNUNET_OK;
-}
-
-/**
- * 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 !=
+ TALER_CONFIG_parse_currencies (TEH_cfg,
+ &num_cspecs,
+ &cspecs))
+ return GNUNET_SYSERR;
+ for (unsigned int i = 0; i<num_cspecs; i++)
{
- char *kyc_mode;
+ struct TALER_CurrencySpecification *cspec;
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- "exchange",
- "KYC_MODE",
- &kyc_mode))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "KYC_MODE");
- return GNUNET_SYSERR;
- }
- if (0 == strcasecmp (kyc_mode,
- "NONE"))
- {
- TEH_kyc_config.mode = TEH_KYC_NONE;
- }
- else if (0 == strcasecmp (kyc_mode,
- "OAUTH2"))
- {
- TEH_kyc_config.mode = TEH_KYC_OAUTH2;
- if (GNUNET_OK !=
- parse_kyc_oauth_cfg ())
- {
- GNUNET_free (kyc_mode);
- return GNUNET_SYSERR;
- }
- }
- else
+ cspec = &cspecs[i];
+ if (0 == strcmp (TEH_currency,
+ cspec->currency))
{
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "KYC_MODE",
- "Must be 'NONE' or 'OAUTH2'");
- GNUNET_free (kyc_mode);
- return GNUNET_SYSERR;
+ TEH_cspec = cspec;
+ break;
}
- GNUNET_free (kyc_mode);
}
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_number (TEH_cfg,
- "exchange",
- "MAX_REQUESTS",
- &req_max))
+ if (NULL == TEH_cspec)
{
- req_max = ULLONG_MAX;
+ 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 !=
- GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
- "exchangedb",
- "IDLE_RESERVE_EXPIRATION_TIME",
- &TEH_reserve_closing_delay))
+ TALER_config_get_amount (TEH_cfg,
+ "exchange",
+ "AML_THRESHOLD",
+ &TEH_aml_threshold))
{
- 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);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Need amount in section `exchange' under `AML_THRESHOLD'\n");
+ return GNUNET_SYSERR;
}
-
if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
- "exchange",
- "MAX_KEYS_CACHING",
- &TEH_max_keys_caching))
+ TALER_config_get_amount (TEH_cfg,
+ "exchange",
+ "STEFAN_ABS",
+ &TEH_stefan_abs))
{
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &TEH_stefan_abs));
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (TEH_cfg,
"exchange",
- "MAX_KEYS_CACHING",
- "valid relative time expected");
- return GNUNET_SYSERR;
+ "STEFAN_LOG",
+ &TEH_stefan_log))
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &TEH_stefan_log));
}
if (GNUNET_OK !=
- TALER_config_get_currency (TEH_cfg,
- &TEH_currency))
+ GNUNET_CONFIGURATION_get_value_float (TEH_cfg,
+ "exchange",
+ "STEFAN_LIN",
+ &TEH_stefan_lin))
{
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "taler",
- "CURRENCY");
+ 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 !=
@@ -1502,35 +2245,6 @@ exchange_serve_process_config (void)
return GNUNET_SYSERR;
}
- if (TEH_KYC_NONE != TEH_kyc_config.mode)
- {
- if (GNUNET_YES ==
- GNUNET_CONFIGURATION_have_value (TEH_cfg,
- "exchange",
- "KYC_WALLET_BALANCE_LIMIT"))
- {
- if ( (GNUNET_OK !=
- TALER_config_get_amount (TEH_cfg,
- "exchange",
- "KYC_WALLET_BALANCE_LIMIT",
- &TEH_kyc_config.wallet_balance_limit)) ||
- (0 != strcasecmp (TEH_currency,
- TEH_kyc_config.wallet_balance_limit.currency)) )
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "KYC_WALLET_BALANCE_LIMIT",
- "valid amount expected");
- return GNUNET_SYSERR;
- }
- }
- else
- {
- memset (&TEH_kyc_config.wallet_balance_limit,
- 0,
- sizeof (TEH_kyc_config.wallet_balance_limit));
- }
- }
{
char *master_public_key_str;
@@ -1546,34 +2260,58 @@ exchange_serve_process_config (void)
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))
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "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);
}
- if (TEH_KYC_NONE != TEH_kyc_config.mode)
+
{
+ char *attr_enc_key_str;
+
if (GNUNET_OK !=
- parse_kyc_settings ())
+ 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);
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Launching exchange with public key `%s'...\n",
- GNUNET_p2s (&TEH_master_public_key.eddsa_pub));
- if (NULL ==
- (TEH_plugin = TALER_EXCHANGEDB_plugin_load (TEH_cfg)))
+ for (unsigned int i = 0; i<MAX_DB_RETRIES; i++)
+ {
+ TEH_plugin = TALER_EXCHANGEDB_plugin_load (TEH_cfg);
+ if (NULL != TEH_plugin)
+ break;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to connect to DB, will try again %u times\n",
+ MAX_DB_RETRIES - i);
+ sleep (1);
+ }
+ if (NULL == TEH_plugin)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to initialize DB subsystem\n");
+ "Failed to initialize DB subsystem. Giving up.\n");
return GNUNET_SYSERR;
}
return GNUNET_OK;
@@ -1718,7 +2456,9 @@ run_single_request (void)
xfork = fork ();
if (-1 == xfork)
{
- global_ret = EXIT_FAILURE;
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "fork");
+ global_ret = EXIT_NO_RESTART;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -1805,11 +2545,17 @@ do_shutdown (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)
+ {
MHD_stop_daemon (mhd);
+ mhd = NULL;
+ }
TEH_wire_done ();
TEH_extensions_done ();
TEH_keys_finished ();
@@ -1828,6 +2574,12 @@ do_shutdown (void *cls)
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;
}
@@ -1865,31 +2617,48 @@ run (void *cls,
GNUNET_SCHEDULER_shutdown ();
return;
}
+ 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))
{
- global_ret = EXIT_FAILURE;
+ GNUNET_break (0);
+ global_ret = EXIT_NO_RESTART;
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
TEH_extensions_init ())
{
- global_ret = EXIT_FAILURE;
+ global_ret = EXIT_NOTINSTALLED;
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
TEH_keys_init ())
{
- global_ret = EXIT_FAILURE;
+ GNUNET_break (0);
+ global_ret = EXIT_NO_RESTART;
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
TEH_wire_init ())
{
- global_ret = EXIT_FAILURE;
+ GNUNET_break (0);
+ global_ret = EXIT_NO_RESTART;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -1901,7 +2670,7 @@ run (void *cls,
if (NULL == TEH_curl_ctx)
{
GNUNET_break (0);
- global_ret = EXIT_FAILURE;
+ global_ret = EXIT_NO_RESTART;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -1940,8 +2709,8 @@ run (void *cls,
MHD_OPTION_CONNECTION_TIMEOUT,
connection_timeout,
(0 == allow_address_reuse)
- ? MHD_OPTION_END
- : MHD_OPTION_LISTENING_ADDRESS_REUSE,
+ ? MHD_OPTION_END
+ : MHD_OPTION_LISTENING_ADDRESS_REUSE,
(unsigned int) allow_address_reuse,
MHD_OPTION_END);
if (NULL == mhd)
@@ -1954,7 +2723,6 @@ run (void *cls,
global_ret = EXIT_SUCCESS;
TALER_MHD_daemon_start (mhd);
atexit (&write_stats);
-
#if HAVE_DEVELOPER
if (NULL != input_filename)
run_single_request ();
diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h
index 4f04029e6..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,107 +25,13 @@
#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>
/**
- * Enumeration for our KYC modes.
- */
-enum TEH_KycMode
-{
- /**
- * KYC is disabled.
- */
- TEH_KYC_NONE = 0,
-
- /**
- * We use Oauth2.0.
- */
- TEH_KYC_OAUTH2 = 1
-};
-
-
-/**
- * Structure describing our KYC configuration.
- */
-struct TEH_KycOptions
-{
- /**
- * What KYC mode are we in?
- */
- enum TEH_KycMode mode;
-
- /**
- * Maximum amount that can be withdrawn in @e withdraw_period without
- * needing KYC.
- * Only valid if @e mode is not #TEH_KYC_NONE and
- * if @e withdraw_period is non-zero.
- */
- struct TALER_Amount withdraw_limit;
-
- /**
- * Maximum balance a wallet can hold without
- * needing KYC.
- * Only valid if @e mode is not #TEH_KYC_NONE and
- * if the amount specified is valid.
- */
- struct TALER_Amount wallet_balance_limit;
-
- /**
- * Time period over which @e withdraw_limit applies.
- * Only valid if @e mode is not #TEH_KYC_NONE.
- */
- struct GNUNET_TIME_Relative withdraw_period;
-
- /**
- * Details depending on @e mode.
- */
- union
- {
-
- /**
- * Configuration details if @e mode is #TEH_KYC_OAUTH2.
- */
- struct
- {
-
- /**
- * URL of the OAuth2.0 endpoint for KYC checks.
- */
- char *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;
-
- } oauth2;
-
- } details;
-};
-
-
-extern struct TEH_KycOptions TEH_kyc_config;
-
-/**
* How long is caching /keys allowed at most?
*/
extern struct GNUNET_TIME_Relative TEH_max_keys_caching;
@@ -159,6 +65,11 @@ extern int TEH_check_invariants_flag;
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;
@@ -177,16 +88,53 @@ extern bool TEH_suicide;
extern struct TALER_MasterPublicKeyP TEH_master_public_key;
/**
+ * Key used to encrypt KYC attribute data in our database.
+ */
+extern struct TALER_AttributeEncryptionKeyP TEH_attribute_key;
+
+/**
* Our DB plugin.
*/
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;
@@ -201,13 +149,11 @@ extern volatile bool MHD_terminating;
*/
extern struct GNUNET_CURL_Context *TEH_curl_ctx;
-/**
- * The manifest of the available extensions, NULL terminated
+/*
+ * Signature of the offline master key of all enabled extensions' configuration
*/
-extern struct TALER_Extension **TEH_extensions;
-
-#define TEH_extension_enabled(ext) (0 <= ext && TALER_Extension_Max > ext && \
- NULL != TEH_extensions[ext]->config)
+extern struct TALER_MasterSignatureP TEH_extensions_sig;
+extern bool TEH_extensions_signed;
/**
* @brief Struct describing an URL and the handler for it.
@@ -253,6 +199,11 @@ struct TEH_RequestContext
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.
@@ -292,11 +243,10 @@ 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 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
*/
@@ -306,11 +256,11 @@ struct TEH_RequestHandler
/**
- * Function to call to handle a POST request.
+ * Function to call to handle POST requests.
*
* @param rc context for the request
* @param json uploaded JSON data
- * @param args array of arguments, needs to be of length @e args_expected
+ * @param args array of arguments, needs to be of length @e nargs
* @return MHD result code
*/
MHD_RESULT
@@ -318,18 +268,18 @@ struct TEH_RequestHandler
const json_t *root,
const char *const args[]);
- } handler;
-
- /**
- * Number of arguments this handler expects in the @a args array.
- */
- unsigned int nargs;
+ /**
+ * 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[]);
- /**
- * 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;
+ } handler;
/**
* Mime type to use in reply (hint, can be NULL).
@@ -350,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
index 1b8af311c..9d3239e86 100644
--- a/src/exchange/taler-exchange-httpd_auditors.c
+++ b/src/exchange/taler-exchange-httpd_auditors.c
@@ -45,7 +45,7 @@ struct AddAuditorDenomContext
/**
* Denomination this is about.
*/
- const struct TALER_DenominationHash *h_denom_pub;
+ const struct TALER_DenominationHashP *h_denom_pub;
/**
* Auditor this is about.
@@ -140,6 +140,7 @@ add_auditor_denom_sig (void *cls,
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,
@@ -150,10 +151,7 @@ add_auditor_denom_sig (void *cls,
meta.expire_deposit,
meta.expire_legal,
&meta.value,
- &meta.fee_withdraw,
- &meta.fee_deposit,
- &meta.fee_refresh,
- &meta.fee_refund,
+ &meta.fees,
awc->auditor_pub,
&awc->auditor_sig))
{
@@ -192,7 +190,7 @@ MHD_RESULT
TEH_handler_auditors (
struct MHD_Connection *connection,
const struct TALER_AuditorPublicKeyP *auditor_pub,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const json_t *root)
{
struct AddAuditorDenomContext awc = {
@@ -216,7 +214,7 @@ TEH_handler_auditors (
return MHD_YES; /* failure */
ret = TEH_DB_run_transaction (connection,
"add auditor denom sig",
- TEH_MT_OTHER,
+ TEH_MT_REQUEST_OTHER,
&res,
&add_auditor_denom_sig,
&awc);
diff --git a/src/exchange/taler-exchange-httpd_auditors.h b/src/exchange/taler-exchange-httpd_auditors.h
index 00a2e57a9..5d5c3a49c 100644
--- a/src/exchange/taler-exchange-httpd_auditors.h
+++ b/src/exchange/taler-exchange-httpd_auditors.h
@@ -39,7 +39,7 @@ MHD_RESULT
TEH_handler_auditors (
struct MHD_Connection *connection,
const struct TALER_AuditorPublicKeyP *auditor_pub,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const json_t *root);
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 a4d598a69..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
*/
MHD_RESULT
-TEH_handler_deposit (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const json_t *root);
+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_withdraw.h b/src/exchange/taler-exchange-httpd_csr.h
index 8d2d8c182..615255f94 100644
--- a/src/exchange/taler-exchange-httpd_withdraw.h
+++ b/src/exchange/taler-exchange-httpd_csr.h
@@ -14,36 +14,43 @@
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
+ * @file taler-exchange-httpd_csr.h
+ * @brief Handle /csr-* requests
+ * @author Lucien Heuzeveldt
+ * @author Gian Demarmles
*/
-#ifndef TALER_EXCHANGE_HTTPD_WITHDRAW_H
-#define TALER_EXCHANGE_HTTPD_WITHDRAW_H
+#ifndef TALER_EXCHANGE_HTTPD_CSR_H
+#define TALER_EXCHANGE_HTTPD_CSR_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.
+ * Handle a "/csr-melt" request.
*
* @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 empty array
* @return MHD result code
*/
MHD_RESULT
-TEH_handler_withdraw (struct TEH_RequestContext *rc,
+TEH_handler_csr_melt (struct TEH_RequestContext *rc,
const json_t *root,
- const char *const args[2]);
+ 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 3600d7931..6fec3fee4 100644
--- a/src/exchange/taler-exchange-httpd_db.c
+++ b/src/exchange/taler-exchange-httpd_db.c
@@ -19,9 +19,12 @@
* @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"
@@ -29,19 +32,6 @@
#include "taler-exchange-httpd_responses.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
-
-
enum GNUNET_DB_QueryStatus
TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
struct MHD_Connection *connection,
@@ -49,15 +39,15 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
MHD_RESULT *mhd_ret)
{
enum TALER_EXCHANGEDB_CoinKnownStatus cks;
- struct TALER_DenominationHash h_denom_pub;
- struct TALER_AgeHash age_hash;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_AgeCommitmentHash h_age_commitment = {{{0}}};
/* make sure coin is 'known' in database */
cks = TEH_plugin->ensure_coin_known (TEH_plugin->cls,
coin,
known_coin_id,
&h_denom_pub,
- &age_hash);
+ &h_age_commitment);
switch (cks)
{
case TALER_EXCHANGEDB_CKS_ADDED:
@@ -74,16 +64,50 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
case TALER_EXCHANGEDB_CKS_DENOM_CONFLICT:
- *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
- &coin->coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- case TALER_EXCHANGEDB_CKS_AGE_CONFLICT:
- *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
+ /* 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,
- &coin->coin_pub);
+ cks,
+ &h_denom_pub,
+ &coin->coin_pub,
+ &h_age_commitment);
return GNUNET_DB_STATUS_HARD_ERROR;
}
GNUNET_assert (0);
@@ -94,7 +118,7 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
enum GNUNET_GenericReturnValue
TEH_DB_run_transaction (struct MHD_Connection *connection,
const char *name,
- enum TEH_MetricType mt,
+ enum TEH_MetricTypeRequest mt,
MHD_RESULT *mhd_ret,
TEH_DB_TransactionCallback cb,
void *cb_cls)
@@ -112,7 +136,7 @@ TEH_DB_run_transaction (struct MHD_Connection *connection,
NULL);
return GNUNET_SYSERR;
}
- GNUNET_assert (mt < TEH_MT_COUNT);
+ GNUNET_assert (mt < TEH_MT_REQUEST_COUNT);
TEH_METRICS_num_requests[mt]++;
for (unsigned int retries = 0;
retries < MAX_TRANSACTION_COMMIT_RETRIES;
@@ -136,10 +160,12 @@ TEH_DB_run_transaction (struct MHD_Connection *connection,
connection,
mhd_ret);
if (0 > qs)
+ {
TEH_plugin->rollback (TEH_plugin->cls);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- return GNUNET_SYSERR;
- if (0 <= qs)
+ 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)
@@ -162,6 +188,7 @@ TEH_DB_run_transaction (struct MHD_Connection *connection,
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);
diff --git a/src/exchange/taler-exchange-httpd_db.h b/src/exchange/taler-exchange-httpd_db.h
index 7c954ffe1..482bc5923 100644
--- a/src/exchange/taler-exchange-httpd_db.h
+++ b/src/exchange/taler-exchange-httpd_db.h
@@ -28,6 +28,19 @@
/**
+ * 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
+
+
+/**
* Ensure coin is known in the database, and handle conflicts and errors.
*
* @param coin the coin to make known
@@ -83,7 +96,7 @@ typedef enum GNUNET_DB_QueryStatus
enum GNUNET_GenericReturnValue
TEH_DB_run_transaction (struct MHD_Connection *connection,
const char *name,
- enum TEH_MetricType mt,
+ 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 84741b5c3..000000000
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ /dev/null
@@ -1,474 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU 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_exchangedb_lib.h"
-#include "taler-exchange-httpd_keys.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_extensions hash of applicable extensions
- * @param h_contract_terms hash of contract details
- * @param exchange_timestamp exchange'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 MHD_RESULT
-reply_deposit_success (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantWireHash *h_wire,
- const struct TALER_ExtensionContractHash *h_extensions,
- const struct TALER_PrivateContractHash *h_contract_terms,
- struct GNUNET_TIME_Timestamp exchange_timestamp,
- struct GNUNET_TIME_Timestamp refund_deadline,
- struct GNUNET_TIME_Timestamp wire_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,
- .exchange_timestamp = GNUNET_TIME_timestamp_hton (exchange_timestamp),
- .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline),
- .wire_deadline = GNUNET_TIME_timestamp_hton (wire_deadline),
- .coin_pub = *coin_pub,
- .merchant_pub = *merchant
- };
- enum TALER_ErrorCode ec;
-
- if (NULL != h_extensions)
- dc.h_extensions = *h_extensions;
- TALER_amount_hton (&dc.amount_without_fee,
- amount_without_fee);
- if (TALER_EC_NONE !=
- (ec = TEH_keys_exchange_sign (&dc,
- &pub,
- &sig)))
- {
- 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",
- exchange_timestamp),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub));
-}
-
-
-/**
- * Closure for #deposit_transaction.
- */
-struct DepositContext
-{
- /**
- * Information about the deposit request.
- */
- const struct TALER_EXCHANGEDB_Deposit *deposit;
-
- /**
- * 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;
-
- /**
- * Hash of the payto URI.
- */
- struct TALER_PaytoHash h_payto;
-
- /**
- * Row of of the coin in the known_coins table.
- */
- uint64_t known_coin_id;
-
-};
-
-
-/**
- * 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[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 DepositContext *dc = cls;
- enum GNUNET_DB_QueryStatus qs;
- bool balance_ok;
- bool in_conflict;
-
- qs = TEH_plugin->do_deposit (TEH_plugin->cls,
- dc->deposit,
- dc->known_coin_id,
- &dc->h_payto,
- false, /* FIXME-OEC: extension blocked */
- &dc->exchange_timestamp,
- &balance_ok,
- &in_conflict);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return 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_GENERIC_DB_STORE_FAILED,
- "deposit");
- return qs;
- }
- if (in_conflict)
- {
- TEH_plugin->rollback (TEH_plugin->cls);
- *mhd_ret
- = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT,
- &dc->deposit->coin.coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (! balance_ok)
- {
- TEH_plugin->rollback (TEH_plugin->cls);
- *mhd_ret
- = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
- &dc->deposit->coin.coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- return qs;
-}
-
-
-/**
- * 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
- */
-MHD_RESULT
-TEH_handler_deposit (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const json_t *root)
-{
- struct DepositContext dc;
- struct TALER_EXCHANGEDB_Deposit deposit;
- const char *payto_uri;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("merchant_payto_uri",
- &payto_uri),
- GNUNET_JSON_spec_fixed_auto ("wire_salt",
- &deposit.wire_salt),
- TALER_JSON_spec_amount ("contribution",
- TEH_currency,
- &deposit.amount_with_fee),
- GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
- &deposit.coin.denom_pub_hash),
- TALER_JSON_spec_denom_sig ("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 ("coin_sig",
- &deposit.csig),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &deposit.timestamp),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &deposit.refund_deadline)),
- GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
- &deposit.wire_deadline),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_MerchantWireHash h_wire;
-
- memset (&deposit,
- 0,
- sizeof (deposit));
- deposit.coin.coin_pub = *coin_pub;
- {
- 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 (payto_uri);
- 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 (deposit.refund_deadline,
- >,
- deposit.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);
- }
- deposit.receiver_wire_account = (char *) payto_uri;
- TALER_payto_hash (payto_uri,
- &dc.h_payto);
- TALER_merchant_wire_signature_hash (payto_uri,
- &deposit.wire_salt,
- &h_wire);
- dc.deposit = &deposit;
-
- /* new deposit */
- dc.exchange_timestamp = GNUNET_TIME_timestamp_get ();
- /* check denomination exists and is valid */
- {
- struct TEH_DenominationKey *dk;
- MHD_RESULT mret;
-
- dk = TEH_keys_denomination_by_hash (&deposit.coin.denom_pub_hash,
- connection,
- &mret);
- if (NULL == dk)
- {
- GNUNET_JSON_parse_free (spec);
- return mret;
- }
- 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 TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
- &deposit.coin.denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
- "DEPOSIT");
- }
- if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
- {
- /* This denomination is not yet valid */
- GNUNET_JSON_parse_free (spec);
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
- &deposit.coin.denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
- "DEPOSIT");
- }
- if (dk->recoup_possible)
- {
- /* This denomination has been revoked */
- GNUNET_JSON_parse_free (spec);
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
- &deposit.coin.denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
- "DEPOSIT");
- }
-
- deposit.deposit_fee = dk->meta.fee_deposit;
- /* check coin signature */
- if (GNUNET_YES !=
- TALER_test_coin_valid (&deposit.coin,
- &dk->denom_pub))
- {
- TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_UNAUTHORIZED,
- TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
- NULL);
- }
- }
- 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_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
- NULL);
- }
-
- if (GNUNET_OK !=
- TALER_wallet_deposit_verify (&deposit.amount_with_fee,
- &deposit.deposit_fee,
- &h_wire,
- &deposit.h_contract_terms,
- NULL /* h_extensions! */,
- &deposit.coin.denom_pub_hash,
- deposit.timestamp,
- &deposit.merchant_pub,
- deposit.refund_deadline,
- &deposit.coin.coin_pub,
- &deposit.csig))
- {
- 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_EXCHANGE_DEPOSIT_COIN_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");
- }
-
- {
- MHD_RESULT mhd_ret = MHD_NO;
- enum GNUNET_DB_QueryStatus qs;
-
- /* make sure coin is 'known' in database */
- qs = TEH_make_coin_known (&deposit.coin,
- connection,
- &dc.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;
- }
-
-
- /* execute transaction */
- {
- MHD_RESULT mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "execute deposit",
- TEH_MT_DEPOSIT,
- &mhd_ret,
- &deposit_transaction,
- &dc))
- {
- GNUNET_JSON_parse_free (spec);
- return mhd_ret;
- }
- }
-
- /* generate regular response */
- {
- struct TALER_Amount amount_without_fee;
- MHD_RESULT res;
-
- GNUNET_assert (0 <=
- TALER_amount_subtract (&amount_without_fee,
- &deposit.amount_with_fee,
- &deposit.deposit_fee));
- res = reply_deposit_success (connection,
- &deposit.coin.coin_pub,
- &h_wire,
- NULL /* h_extensions! */,
- &deposit.h_contract_terms,
- dc.exchange_timestamp,
- deposit.refund_deadline,
- deposit.wire_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 9a33f2b71..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, 2021 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,6 +23,7 @@
#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"
@@ -32,83 +33,52 @@
/**
- * 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
+ * Closure for #handle_wtid_data.
*/
-static MHD_RESULT
-reply_deposit_details (struct MHD_Connection *connection,
- const struct TALER_PrivateContractHash *h_contract_terms,
- const struct TALER_MerchantWireHash *h_wire,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *coin_contribution,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- struct GNUNET_TIME_Timestamp exec_time)
+struct DepositWtidContext
{
- 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 (exec_time)
- };
- enum TALER_ErrorCode ec;
- TALER_amount_hton (&cw.coin_contribution,
- coin_contribution);
- if (TALER_EC_NONE !=
- (ec = TEH_keys_exchange_sign (&cw,
- &pub,
- &sig)))
- {
- return TALER_MHD_reply_with_ec (connection,
- ec,
- NULL);
- }
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_data_auto ("wtid",
- wtid),
- GNUNET_JSON_pack_timestamp ("execution_time",
- exec_time),
- TALER_JSON_pack_amount ("coin_contribution",
- coin_contribution),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub));
-}
+ /**
+ * 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;
-/**
- * Closure for #handle_wtid_data.
- */
-struct DepositWtidContext
-{
+ /**
+ * 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;
/**
- * Deposit details.
+ * Hash over the wiring information of the merchant.
*/
- const struct TALER_DepositTrackPS *tps;
+ struct TALER_MerchantWireHashP h_wire;
/**
- * Public key of the merchant.
+ * The Merchant's public key. The deposit inquiry request is to be
+ * signed by the corresponding private key (using EdDSA).
*/
- const struct TALER_MerchantPublicKeyP *merchant_pub;
+ 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;
/**
* Set by #handle_wtid data to the wire transfer ID.
@@ -116,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;
@@ -131,6 +107,11 @@ struct DepositWtidContext
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
* (that is, @e coin_contribution minus @e coin_fee).
*/
@@ -142,15 +123,107 @@ struct DepositWtidContext
struct TALER_EXCHANGEDB_KycStatus kyc;
/**
+ * AML status information for the receiving account.
+ */
+ enum TALER_AmlDecisionState aml_decision;
+
+ /**
* Set to #GNUNET_YES by #handle_wtid if the wire transfer is still pending
* (and the above were not set).
* Set to #GNUNET_SYSERR if there was a serious error.
*/
enum GNUNET_GenericReturnValue pending;
+
+ /**
+ * #GNUNET_YES if we were suspended, #GNUNET_SYSERR
+ * if we were woken up due to shutdown.
+ */
+ enum GNUNET_GenericReturnValue suspended;
};
/**
+ * Head of DLL of suspended requests.
+ */
+static struct DepositWtidContext *dwc_head;
+
+/**
+ * Tail of DLL of suspended requests.
+ */
+static struct DepositWtidContext *dwc_tail;
+
+
+void
+TEH_deposits_get_cleanup ()
+{
+ struct DepositWtidContext *n;
+
+ for (struct DepositWtidContext *ctx = dwc_head;
+ NULL != ctx;
+ ctx = n)
+ {
+ 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);
+ }
+}
+
+
+/**
+ * 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);
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ }
+ 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));
+}
+
+
+/**
* Execute a "deposits" GET. Returns the transfer information
* associated with the given deposit.
*
@@ -177,17 +250,17 @@ deposits_get_transaction (void *cls,
struct TALER_Amount fee;
qs = TEH_plugin->lookup_transfer_by_deposit (TEH_plugin->cls,
- &ctx->tps->h_contract_terms,
- &ctx->tps->h_wire,
- &ctx->tps->coin_pub,
- ctx->merchant_pub,
-
+ &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->kyc,
+ &ctx->aml_decision);
if (0 > qs)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
@@ -224,55 +297,140 @@ deposits_get_transaction (void *cls,
/**
+ * Function called on events received from Postgres.
+ * Wakes up long pollers.
+ *
+ * @param cls the `struct DepositWtidContext *`
+ * @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 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);
+}
+
+
+/**
* Lookup and return the wire transfer identifier.
*
- * @param connection the MHD connection to handle
- * @param tps signed request to execute
- * @param merchant_pub public key from the merchant
+ * @param ctx context of the signed request to execute
* @return MHD result code
*/
static MHD_RESULT
handle_track_transaction_request (
- struct MHD_Connection *connection,
- const struct TALER_DepositTrackPS *tps,
- const struct TALER_MerchantPublicKeyP *merchant_pub)
+ struct DepositWtidContext *ctx)
{
- MHD_RESULT mhd_ret;
- struct DepositWtidContext ctx = {
- .tps = tps,
- .merchant_pub = merchant_pub
- };
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "handle deposits GET",
- TEH_MT_OTHER,
- &mhd_ret,
- &deposits_get_transaction,
- &ctx))
- return mhd_ret;
- if (GNUNET_SYSERR == ctx.pending)
+ struct MHD_Connection *connection = ctx->rc->connection;
+
+ if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) &&
+ (NULL == ctx->eh) )
+ {
+ 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);
+ }
+ {
+ 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_SYSERR == ctx->pending)
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
"wire fees exceed aggregate in database");
- if (GNUNET_YES == ctx.pending)
+ 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_uint64 ("payment_target_uuid",
- ctx.kyc.payment_target_uuid),
+ 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),
+ ctx->kyc.ok),
GNUNET_JSON_pack_timestamp ("execution_time",
- ctx.execution_time));
+ ctx->execution_time));
+ }
return reply_deposit_details (connection,
- &tps->h_contract_terms,
- &tps->h_wire,
- &tps->coin_pub,
- &ctx.coin_delta,
- &ctx.wtid,
- ctx.execution_time);
+ 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)
+ {
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ ctx->eh);
+ ctx->eh = NULL;
+ }
+ GNUNET_free (ctx);
}
@@ -280,85 +438,87 @@ MHD_RESULT
TEH_handler_deposits_get (struct TEH_RequestContext *rc,
const char *const args[4])
{
- enum GNUNET_GenericReturnValue res;
- struct TALER_MerchantSignatureP merchant_sig;
- struct TALER_DepositTrackPS tps = {
- .purpose.size = htonl (sizeof (tps)),
- .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION)
- };
-
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &tps.h_wire,
- sizeof (tps.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]),
- &tps.merchant,
- sizeof (tps.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]),
- &tps.h_contract_terms,
- sizeof (tps.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]),
- &tps.coin_pub,
- sizeof (tps.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]);
- }
- res = TALER_MHD_parse_request_arg_data (rc->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,
- &merchant_sig.eddsa_sig,
- &tps.merchant.eddsa_pub))
+ struct DepositWtidContext *ctx = rc->rh_ctx;
+
+ if (NULL == ctx)
{
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID,
- NULL);
+ 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 (rc->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 aee7521a5..c7b1698bb 100644
--- a/src/exchange/taler-exchange-httpd_deposits_get.h
+++ b/src/exchange/taler-exchange-httpd_deposits_get.h
@@ -27,6 +27,13 @@
/**
+ * 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.
*
diff --git a/src/exchange/taler-exchange-httpd_extensions.c b/src/exchange/taler-exchange-httpd_extensions.c
index 8723bebc8..d62a618ae 100644
--- a/src/exchange/taler-exchange-httpd_extensions.c
+++ b/src/exchange/taler-exchange-httpd_extensions.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021, 2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -14,119 +14,22 @@
*/
/**
* @file taler-exchange-httpd_extensions.c
- * @brief Handle extensions (age-restriction, peer2peer)
+ * @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>
/**
- * @brief implements the TALER_Extension.parse_and_set_config interface.
- */
-static enum GNUNET_GenericReturnValue
-age_restriction_parse_and_set_config (struct TALER_Extension *this,
- const json_t *config)
-{
- enum GNUNET_GenericReturnValue ret;
- struct TALER_AgeMask mask = {0};
-
- ret = TALER_agemask_parse_json (config, &mask);
- if (GNUNET_OK != ret)
- return ret;
-
- if (this != NULL && TALER_Extension_AgeRestriction == this->type)
- {
- if (NULL != this->config)
- {
- GNUNET_free (this->config);
- }
- this->config = GNUNET_malloc (sizeof(struct TALER_AgeMask));
- GNUNET_memcpy (this->config, &mask, sizeof(struct TALER_AgeMask));
- }
-
- return GNUNET_OK;
-}
-
-
-/**
- * @brief implements the TALER_Extension.test_config interface.
- */
-static enum GNUNET_GenericReturnValue
-age_restriction_test_config (const json_t *config)
-{
- return age_restriction_parse_and_set_config (NULL, config);
-}
-
-
-/**
- * @brief implements the TALER_Extension.config_to_json interface.
- */
-static json_t *
-age_restriction_config_to_json (const struct TALER_Extension *this)
-{
- const struct TALER_AgeMask *mask;
- if (NULL == this || TALER_Extension_AgeRestriction != this->type)
- return NULL;
-
- mask = (struct TALER_AgeMask *) this->config;
- json_t *config = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("extension", this->name),
- GNUNET_JSON_pack_string ("mask",
- TALER_age_mask_to_string (mask))
- );
-
- return config;
-}
-
-
-/* The extension for age restriction */
-static struct TALER_Extension extension_age_restriction = {
- .type = TALER_Extension_AgeRestriction,
- .name = "age_restriction",
- .critical = false,
- .config = NULL, // disabled per default
- .test_config = &age_restriction_test_config,
- .parse_and_set_config = &age_restriction_parse_and_set_config,
- .config_to_json = &age_restriction_config_to_json,
-};
-
-/* TODO: The extension for peer2peer */
-static struct TALER_Extension extension_peer2peer = {
- .type = TALER_Extension_Peer2Peer,
- .name = "peer2peer",
- .critical = false,
- .config = NULL, // disabled per default
- .test_config = NULL, // TODO
- .parse_and_set_config = NULL, // TODO
- .config_to_json = NULL, // TODO
-};
-
-
-/**
- * Create a list with the extensions for Age Restriction and Peer2Peer
- */
-static struct TALER_Extension **
-get_known_extensions ()
-{
-
- struct TALER_Extension **list = GNUNET_new_array (TALER_Extension_Max + 1,
- struct TALER_Extension *);
- list[TALER_Extension_AgeRestriction] = &extension_age_restriction;
- list[TALER_Extension_Peer2Peer] = &extension_peer2peer;
- list[TALER_Extension_Max] = NULL;
-
- return list;
-}
-
-
-/**
* Handler listening for extensions updates by other exchange
* services.
*/
@@ -137,8 +40,8 @@ static struct GNUNET_DB_EventHandler *extensions_eh;
* the extensions data in the database.
*
* @param cls NULL
- * @param extra unused
- * @param extra_size number of bytes in @a extra unused
+ * @param extra type of the extension
+ * @param extra_size number of bytes in @a extra
*/
static void
extension_update_event_cb (void *cls,
@@ -146,12 +49,14 @@ extension_update_event_cb (void *cls,
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(enum TALER_Extension_Type) != extra_size)
+ if (sizeof(nbo_type) != extra_size)
{
GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -159,87 +64,167 @@ extension_update_event_cb (void *cls,
return;
}
- type = *(enum TALER_Extension_Type *) extra;
- if (type <0 || type >= TALER_Extension_Max)
+ 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, incorrect type for TALER_Extension_type\n");
+ "Oops, unknown extension type: %d\n", type);
return;
}
- // Get the config from the database as string
+ // Get the manifest from the database as string
{
- char *config_str;
+ char *manifest_str = NULL;
enum GNUNET_DB_QueryStatus qs;
- struct TALER_Extension *extension;
json_error_t err;
- json_t *config;
+ json_t *manifest_js;
enum GNUNET_GenericReturnValue ret;
- // TODO: make this a safe lookup
- extension = TEH_extensions[type];
-
- qs = TEH_plugin->get_extension_config (TEH_plugin->cls,
- extension->name,
- &config_str);
+ 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 config\n");
+ "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
- config = json_loads (config_str, JSON_DECODE_ANY, &err);
- if (NULL == config)
+ manifest_js = json_loads (manifest_str, JSON_DECODE_ANY, &err);
+ if (NULL == manifest_js)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse config for extension `%s' as JSON: %s (%s)\n",
+ "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->parse_and_set_config (extension, config);
+ 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 database",
- extension->name);
+ "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 ()
{
- TEH_extensions = get_known_extensions ();
+ /* 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 */
{
- struct GNUNET_DB_EventHeaderP ev = {
- .size = htons (sizeof (ev)),
- .type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED),
- };
+ const struct TALER_AgeRestrictionConfig *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;
- }
+ 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;
}
@@ -256,4 +241,202 @@ TEH_extensions_done ()
}
+/*
+ * @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
index 4659b653e..e435f8f03 100644
--- a/src/exchange/taler-exchange-httpd_extensions.h
+++ b/src/exchange/taler-exchange-httpd_extensions.h
@@ -40,4 +40,19 @@ TEH_extensions_init (void);
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
index 30bbe8ebb..0ec28e950 100644
--- a/src/exchange/taler-exchange-httpd_keys.c
+++ b/src/exchange/taler-exchange-httpd_keys.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020, 2021 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -17,12 +17,15 @@
* @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"
@@ -43,24 +46,6 @@
/**
- * 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 "12:0:0"
-
-
-/**
* Information about a denomination on offer by the denomination helper.
*/
struct HelperDenomination
@@ -80,7 +65,7 @@ struct HelperDenomination
/**
* Hash of the full denomination key.
*/
- struct TALER_DenominationHash h_denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
/**
* Signature over this key from the security module's key.
@@ -103,6 +88,11 @@ struct HelperDenomination
*/
struct TALER_RsaPubHashP h_rsa;
+ /**
+ * Hash of the CS key.
+ */
+ struct TALER_CsPubHashP h_cs;
+
} h_details;
/**
@@ -172,10 +162,9 @@ struct HelperSignkey
/**
- * State associated with the crypto helpers / security modules.
- * Created per-thread, but NOT updated when the #key_generation
- * is updated (instead constantly kept in sync whenever
- * #TEH_keys_get_state() is called).
+ * 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
{
@@ -188,7 +177,12 @@ struct HelperState
/**
* Handle for the denom/RSA helper.
*/
- struct TALER_CRYPTO_RsaDenominationHelper *dh;
+ 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.
@@ -201,6 +195,11 @@ struct HelperState
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.
@@ -229,6 +228,11 @@ struct KeysResponseData
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
@@ -264,7 +268,6 @@ struct SigningKey
};
-
struct TEH_KeyStateHandle
{
@@ -282,12 +285,28 @@ struct TEH_KeyStateHandle
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;
*/
@@ -371,13 +390,119 @@ struct SuspendedKeysRequests
/**
+ * 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 (in another thread). See #TEH_keys_get_state() and
+ * something external changed. See #TEH_keys_get_state() and
* #TEH_keys_update_states() for uses of this variable.
*/
static uint64_t key_generation;
@@ -422,9 +547,19 @@ static struct GNUNET_SCHEDULER_Task *keys_tt;
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_sm_pub;
+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.
@@ -438,6 +573,449 @@ 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
@@ -528,7 +1106,7 @@ suspend_request (struct MHD_Connection *connection)
* @param value a `struct TEH_DenominationKey`
* @return #GNUNET_OK
*/
-static int
+static enum GNUNET_GenericReturnValue
check_dk (void *cls,
const struct GNUNET_HashCode *hc,
void *value)
@@ -537,11 +1115,20 @@ check_dk (void *cls,
(void) cls;
(void) hc;
- GNUNET_assert (TALER_DENOMINATION_INVALID != dk->denom_pub.cipher);
- if (TALER_DENOMINATION_RSA == dk->denom_pub.cipher)
+ 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.details.rsa_public_key));
- return GNUNET_OK;
+ 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;
}
@@ -595,6 +1182,7 @@ clear_response_cache (struct TEH_KeyStateHandle *ksh)
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,
@@ -609,19 +1197,43 @@ clear_response_cache (struct TEH_KeyStateHandle *ksh)
* @param sm_pub RSA security module public key to check
*/
static void
-check_denom_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
+check_denom_rsa_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
{
if (0 !=
GNUNET_memcmp (sm_pub,
- &denom_sm_pub))
+ &denom_rsa_sm_pub))
{
- if (! GNUNET_is_zero (&denom_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_sm_pub = *sm_pub; /* TOFU ;-) */
+ 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 ;-) */
}
}
@@ -659,7 +1271,7 @@ check_esign_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
* @param value the `struct HelperDenomination` to release
* @return #GNUNET_OK (continue to iterate)
*/
-static int
+static enum GNUNET_GenericReturnValue
free_denom_cb (void *cls,
const struct GNUNET_HashCode *h_denom_pub,
void *value)
@@ -684,7 +1296,7 @@ free_denom_cb (void *cls,
* @param value the `struct HelperSignkey` to release
* @return #GNUNET_OK (continue to iterate)
*/
-static int
+static enum GNUNET_GenericReturnValue
free_esign_cb (void *cls,
const struct GNUNET_PeerIdentity *pid,
void *value)
@@ -712,6 +1324,8 @@ destroy_key_helpers (struct HelperState *hs)
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,
@@ -719,10 +1333,15 @@ destroy_key_helpers (struct HelperState *hs)
hs);
GNUNET_CONTAINER_multipeermap_destroy (hs->esign_keys);
hs->esign_keys = NULL;
- if (NULL != hs->dh)
+ if (NULL != hs->rsadh)
{
- TALER_CRYPTO_helper_rsa_disconnect (hs->dh);
- hs->dh = NULL;
+ 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)
{
@@ -740,46 +1359,40 @@ destroy_key_helpers (struct HelperState *hs)
* denomination.
*/
static struct TALER_AgeMask
-load_age_mask (const char*section_name)
+load_age_mask (const char *section_name)
{
static const struct TALER_AgeMask null_mask = {0};
- struct TALER_AgeMask age_mask = {0};
- const struct TALER_Extension *age_ext =
- TEH_extensions[TALER_Extension_AgeRestriction];
+ enum GNUNET_GenericReturnValue ret;
- // Get the age mask from the extension, if configured
- if (NULL != age_ext->config)
- {
- age_mask = *(struct TALER_AgeMask *) age_ext->config;
- }
+ if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value (
+ TEH_cfg,
+ section_name,
+ "AGE_RESTRICTED")))
+ return null_mask;
- if (age_mask.mask == 0)
+ if (GNUNET_SYSERR ==
+ (ret = GNUNET_CONFIGURATION_get_value_yesno (TEH_cfg,
+ section_name,
+ "AGE_RESTRICTED")))
{
- /* Age restriction support is not enabled. Ignore the AGE_RESTRICTED field
- * for the particular denomination and simply return the null_mask
- */
+ 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 == (GNUNET_CONFIGURATION_have_value (
- TEH_cfg,
- section_name,
- "AGE_RESTRICTED")))
+ if (GNUNET_OK == ret)
{
- enum GNUNET_GenericReturnValue ret;
- 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 (! 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 age_mask;
+
+ return null_mask;
}
@@ -795,8 +1408,8 @@ load_age_mask (const char*section_name)
* 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_denom_pub hash of the @a denom_pub that is available (or was purged)
- * @param denom_pub the public key itself, NULL if the key was 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.
@@ -808,7 +1421,7 @@ helper_rsa_cb (
struct GNUNET_TIME_Timestamp start_time,
struct GNUNET_TIME_Relative validity_duration,
const struct TALER_RsaPubHashP *h_rsa,
- const struct TALER_DenominationPublicKey *denom_pub,
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
const struct TALER_SecurityModulePublicKeyP *sm_pub,
const struct TALER_SecurityModuleSignatureP *sm_sig)
{
@@ -832,16 +1445,15 @@ helper_rsa_cb (
return;
}
GNUNET_assert (NULL != sm_pub);
- check_denom_sm_pub (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 (TALER_DENOMINATION_RSA == denom_pub->cipher);
- TALER_denom_pub_deep_copy (&hd->denom_pub,
- denom_pub);
- GNUNET_assert (TALER_DENOMINATION_RSA == hd->denom_pub.cipher);
+ 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,
@@ -865,6 +1477,86 @@ helper_rsa_cb (
/**
+ * 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.
@@ -940,18 +1632,32 @@ setup_key_helpers (struct HelperState *hs)
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->dh = TALER_CRYPTO_helper_rsa_connect (TEH_cfg,
- &helper_rsa_cb,
- hs);
- if (NULL == hs->dh)
+ 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)
@@ -971,7 +1677,8 @@ setup_key_helpers (struct HelperState *hs)
static void
sync_key_helpers (struct HelperState *hs)
{
- TALER_CRYPTO_helper_rsa_poll (hs->dh);
+ TALER_CRYPTO_helper_rsa_poll (hs->rsadh);
+ TALER_CRYPTO_helper_cs_poll (hs->csdh);
TALER_CRYPTO_helper_esign_poll (hs->esh);
}
@@ -984,7 +1691,7 @@ sync_key_helpers (struct HelperState *hs)
* @param value a `struct TEH_DenominationKey` to free
* @return #GNUNET_OK (continue to iterate)
*/
-static int
+static enum GNUNET_GenericReturnValue
clear_denomination_cb (void *cls,
const struct GNUNET_HashCode *h_denom_pub,
void *value)
@@ -1015,7 +1722,7 @@ clear_denomination_cb (void *cls,
* @param value a `struct SigningKey` to free
* @return #GNUNET_OK (continue to iterate)
*/
-static int
+static enum GNUNET_GenericReturnValue
clear_signkey_cb (void *cls,
const struct GNUNET_PeerIdentity *pid,
void *value)
@@ -1040,7 +1747,16 @@ 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);
@@ -1051,6 +1767,8 @@ destroy_key_state (struct TEH_KeyStateHandle *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);
@@ -1109,6 +1827,17 @@ TEH_keys_init ()
"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,
@@ -1161,7 +1890,7 @@ static void
denomination_info_cb (
void *cls,
const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
const struct TALER_MasterSignatureP *master_sig,
bool recoup_possible)
@@ -1169,7 +1898,25 @@ denomination_info_cb (
struct TEH_KeyStateHandle *ksh = cls;
struct TEH_DenominationKey *dk;
- GNUNET_assert (TALER_DENOMINATION_INVALID != denom_pub->cipher);
+ 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) ||
@@ -1181,12 +1928,14 @@ denomination_info_cb (
return;
}
dk = GNUNET_new (struct TEH_DenominationKey);
- TALER_denom_pub_deep_copy (&dk->denom_pub,
- denom_pub);
+ 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,
@@ -1215,6 +1964,19 @@ signkey_info_cb (
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;
@@ -1255,7 +2017,7 @@ struct GetAuditorSigsContext
* @param value a `struct TEH_DenominationKey`
* @return #GNUNET_OK (continue to iterate)
*/
-static int
+static enum GNUNET_GenericReturnValue
get_auditor_sigs (void *cls,
const struct GNUNET_HashCode *h_denom_pub,
void *value)
@@ -1336,7 +2098,7 @@ static void
auditor_denom_cb (
void *cls,
const struct TALER_AuditorPublicKeyP *auditor_pub,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_AuditorSignatureP *auditor_sig)
{
struct TEH_KeyStateHandle *ksh = cls;
@@ -1388,7 +2150,7 @@ struct SignKeyCtx
* @param value a `struct SigningKey`
* @return #GNUNET_OK (continue to iterate)
*/
-static int
+static enum GNUNET_GenericReturnValue
add_sign_key_cb (void *cls,
const struct GNUNET_PeerIdentity *pid,
void *value)
@@ -1457,7 +2219,7 @@ struct DenomKeyCtx
* @param value a `struct TEH_DenominationKey`
* @return #GNUNET_OK (continue to iterate)
*/
-static int
+static enum GNUNET_GenericReturnValue
add_denom_key_cb (void *cls,
const struct GNUNET_HashCode *h_denom_pub,
void *value)
@@ -1494,68 +2256,16 @@ add_denom_key_cb (void *cls,
/**
- * 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 ksh the key state to use
+ * @param cls the key state to use
* @param[in,out] response the response to modify
- * @return #GNUNET_OK on success
*/
-static enum GNUNET_GenericReturnValue
-setup_general_response_headers (struct TEH_KeyStateHandle *ksh,
+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);
@@ -1563,27 +2273,38 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh,
MHD_add_response_header (response,
MHD_HTTP_HEADER_CONTENT_TYPE,
"application/json"));
- get_date_string (ksh->reload_time.abs_time,
- dat);
GNUNET_break (MHD_YES ==
MHD_add_response_header (response,
- MHD_HTTP_HEADER_LAST_MODIFIED,
- dat));
+ 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);
- m = GNUNET_TIME_absolute_to_timestamp (a);
- get_date_string (m.abs_time,
- dat);
+ /* 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'\n",
- dat);
+ "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,
@@ -1592,7 +2313,32 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh,
= GNUNET_TIME_timestamp_min (m,
ksh->signature_expires);
}
- return GNUNET_OK;
+ /* 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)));
}
@@ -1601,52 +2347,58 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh,
* @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 denominatoin keys in @a denoms
- * @param last_cpd timestamp to use
- * @param signkeys list of sign keys to return
- * @param recoup list of revoked keys to return
- * @param denoms list of denominations to return
- * @param age_restricted_denoms list of age restricted denominations to return, can be NULL
+ * @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_cpd,
+ struct GNUNET_TIME_Timestamp last_cherry_pick_date,
json_t *signkeys,
json_t *recoup,
- json_t *denoms,
- json_t *age_restricted_denoms)
+ json_t *grouped_denominations)
{
struct KeysResponseData krd;
struct TALER_ExchangePublicKeyP exchange_pub;
struct TALER_ExchangeSignatureP exchange_sig;
+ struct WireStateHandle *wsh;
json_t *keys;
- GNUNET_assert (! GNUNET_TIME_absolute_is_zero (last_cpd.abs_time));
+ 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 != denoms);
+ 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_cpd));
- /* Sign hash over denomination keys */
- {
- struct TALER_ExchangeKeySetPS ks = {
- .purpose.size = htonl (sizeof (ks)),
- .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET),
- .list_issue_date = GNUNET_TIME_timestamp_hton (last_cpd),
- .hc = *denom_keys_hash
- };
+ 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 = TEH_keys_exchange_sign2 (ksh,
- &ks,
- &exchange_pub,
- &exchange_sig)))
+ (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",
@@ -1654,6 +2406,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
return GNUNET_SYSERR;
}
}
+
{
const struct SigningKey *sk;
@@ -1664,11 +2417,33 @@ create_krd (struct TEH_KeyStateHandle *ksh,
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",
@@ -1677,71 +2452,115 @@ create_krd (struct TEH_KeyStateHandle *ksh,
signkeys),
GNUNET_JSON_pack_array_incref ("recoup",
recoup),
- GNUNET_JSON_pack_array_incref ("denoms",
- denoms),
+ 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_cpd),
- GNUNET_JSON_pack_data_auto ("eddsa_pub",
+ last_cherry_pick_date),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
&exchange_pub),
- GNUNET_JSON_pack_data_auto ("eddsa_sig",
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
&exchange_sig));
GNUNET_assert (NULL != keys);
- // Set wallet limit if KYC is configured
- if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
- (GNUNET_OK ==
- TALER_amount_is_valid (&TEH_kyc_config.wallet_balance_limit)) )
+ /* Set wallet limit if KYC is configured */
{
- GNUNET_assert (
- 0 ==
- json_object_set_new (
- keys,
- "wallet_balance_limit_without_kyc",
- TALER_JSON_from_amount (
- &TEH_kyc_config.wallet_balance_limit)));
- }
-
- // Signal support for the age-restriction extension, if so configured, and
- // add the array of age-restricted denominations.
- if (TEH_extension_enabled (TALER_Extension_AgeRestriction) &&
- NULL != age_restricted_denoms)
- {
- struct TALER_AgeMask *mask;
- json_t *config;
-
- mask = (struct
- TALER_AgeMask *) TEH_extensions[TALER_Extension_AgeRestriction]->
- config;
- config = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_bool ("critical", false),
- GNUNET_JSON_pack_string ("version", "1"),
- GNUNET_JSON_pack_string ("age_groups", TALER_age_mask_to_string (mask)));
- GNUNET_assert (NULL != config);
- GNUNET_assert (
- 0 ==
- json_object_set_new (
- keys,
- "age_restriction",
- config));
+ 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));
+ }
- GNUNET_assert (
- 0 ==
- json_object_set_new (
+ /* 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,
- "age_restricted_denoms",
- age_restricted_denoms));
+ "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);
+ }
}
- // TODO: signal support and configuration for the P2P extension, once
- // implemented.
{
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,
@@ -1753,15 +2572,34 @@ create_krd (struct TEH_KeyStateHandle *ksh,
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);
- GNUNET_assert (GNUNET_OK ==
- setup_general_response_headers (ksh,
- 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);
@@ -1777,11 +2615,16 @@ create_krd (struct TEH_KeyStateHandle *ksh,
MHD_add_response_header (krd.response_compressed,
MHD_HTTP_HEADER_CONTENT_ENCODING,
"deflate")) );
- GNUNET_assert (GNUNET_OK ==
- setup_general_response_headers (ksh,
- krd.response_compressed));
+ 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_cpd;
+ krd.cherry_pick_date = last_cherry_pick_date;
GNUNET_array_append (ksh->krd_array,
ksh->krd_array_length,
krd);
@@ -1790,6 +2633,194 @@ create_krd (struct TEH_KeyStateHandle *ksh,
/**
+ * 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
@@ -1801,22 +2832,50 @@ create_krd (struct TEH_KeyStateHandle *ksh,
static enum GNUNET_GenericReturnValue
finish_keys_response (struct TEH_KeyStateHandle *ksh)
{
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
json_t *recoup;
- struct SignKeyCtx sctx;
- json_t *denoms = NULL;
- json_t *age_restricted_denoms = NULL;
- struct GNUNET_TIME_Timestamp last_cpd;
+ 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 GNUNET_HashContext *hash_context;
+ 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);
- sctx.min_sk_frequency = GNUNET_TIME_UNIT_FOREVER_REL;
+ 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);
- recoup = json_array ();
- GNUNET_assert (NULL != recoup);
+ 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 = {
@@ -1832,72 +2891,176 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
= GNUNET_TIME_relative_min (dkc.min_dk_frequency,
sctx.min_sk_frequency);
}
- denoms = json_array ();
- GNUNET_assert (NULL != denoms);
- // If age restriction is enabled, initialize the array of age restricted denoms.
- if (TEH_extension_enabled (TALER_Extension_AgeRestriction))
- {
- age_restricted_denoms = json_array ();
- GNUNET_assert (NULL != age_restricted_denoms);
- }
+ last_cherry_pick_date = GNUNET_TIME_UNIT_ZERO_TS;
- last_cpd = GNUNET_TIME_UNIT_ZERO_TS;
- hash_context = GNUNET_CRYPTO_hash_context_start ();
{
struct TEH_DenominationKey *dk;
+ struct GNUNET_CONTAINER_MultiHashMap *denominations_by_group;
- /* heap = min heap, sorted by start time */
+ 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_cpd,
+ if (GNUNET_TIME_timestamp_cmp (last_cherry_pick_date,
!=,
dk->meta.start) &&
- (! GNUNET_TIME_absolute_is_zero (last_cpd.abs_time)) )
+ (! 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;
- GNUNET_CRYPTO_hash_context_finish (
- GNUNET_CRYPTO_hash_context_copy (hash_context),
- &hc);
+ compute_msig_hash (&sig_ctx,
+ &hc);
if (GNUNET_OK !=
create_krd (ksh,
&hc,
- last_cpd,
+ last_cherry_pick_date,
sctx.signkeys,
recoup,
- denoms,
- age_restricted_denoms))
+ grouped_denominations))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to generate key response data for %s\n",
- GNUNET_TIME_timestamp2s (last_cpd));
- GNUNET_CRYPTO_hash_context_abort (hash_context);
+ 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);
- json_decref (denoms);
- if (NULL != age_restricted_denoms)
- json_decref (age_restricted_denoms);
- json_decref (sctx.signkeys);
- json_decref (recoup);
- return GNUNET_SYSERR;
+ goto CLEANUP;
}
}
- last_cpd = dk->meta.start;
- GNUNET_CRYPTO_hash_context_read (hash_context,
- &dk->h_denom_pub,
- sizeof (struct GNUNET_HashCode));
+ 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.
+ */
{
- json_t *denom;
- json_t *array;
+ 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));
+ }
- denom =
- GNUNET_JSON_PACK (
+ /* 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",
@@ -1906,79 +3069,162 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
dk->meta.expire_deposit),
GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
dk->meta.expire_legal),
- TALER_JSON_pack_denom_pub ("denom_pub",
- &dk->denom_pub),
- TALER_JSON_pack_amount ("value",
- &dk->meta.value),
- TALER_JSON_pack_amount ("fee_withdraw",
- &dk->meta.fee_withdraw),
- TALER_JSON_pack_amount ("fee_deposit",
- &dk->meta.fee_deposit),
- TALER_JSON_pack_amount ("fee_refresh",
- &dk->meta.fee_refresh),
- TALER_JSON_pack_amount ("fee_refund",
- &dk->meta.fee_refund));
-
- /* Put the denom into the correct array - denoms or age_restricted_denoms -
- * depending on the settings and the properties of the denomination */
- if (NULL != age_restricted_denoms &&
- 0 != dk->meta.age_restrictions.mask)
- {
- array = age_restricted_denoms;
- }
- else
- {
- array = denoms;
+ key_spec
+ );
+ GNUNET_assert (NULL != entry);
}
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- array,
- denom));
+ /* 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_cpd.abs_time))
+
+ if (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time))
{
struct GNUNET_HashCode hc;
- GNUNET_CRYPTO_hash_context_finish (hash_context,
- &hc);
+ compute_msig_hash (&sig_ctx,
+ &hc);
if (GNUNET_OK !=
create_krd (ksh,
&hc,
- last_cpd,
+ last_cherry_pick_date,
sctx.signkeys,
recoup,
- denoms,
- age_restricted_denoms))
+ grouped_denominations))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to generate key response data for %s\n",
- GNUNET_TIME_timestamp2s (last_cpd));
- json_decref (denoms);
- if (NULL != age_restricted_denoms)
- json_decref (age_restricted_denoms);
- json_decref (sctx.signkeys);
- json_decref (recoup);
- return GNUNET_SYSERR;
+ 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");
- GNUNET_CRYPTO_hash_context_abort (hash_context);
}
- json_decref (sctx.signkeys);
+ 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);
- json_decref (denoms);
- if (NULL != age_restricted_denoms)
- json_decref (age_restricted_denoms);
- return GNUNET_OK;
+ 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))));
}
@@ -2019,14 +3265,31 @@ build_key_state (struct HelperState *hs,
ksh->helpers = hs;
}
ksh->denomkey_map = GNUNET_CONTAINER_multihashmap_create (1024,
- GNUNET_YES);
+ true);
ksh->signkey_map = GNUNET_CONTAINER_multipeermap_create (32,
- GNUNET_NO /* MUST be NO! */);
+ 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);
@@ -2069,20 +3332,23 @@ build_key_state (struct HelperState *hs,
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 (likely no signing keys available yet)\n");
+ "Could not finish /keys response (required data not configured yet)\n");
destroy_key_state (ksh,
true);
return NULL;
}
+
return ksh;
}
@@ -2104,16 +3370,8 @@ TEH_keys_update_states ()
}
-/**
- * Obtain the key state for the current thread. Should ONLY be used
- * directly if @a management_only is true. Otherwise use #TEH_keys_get_state().
- *
- * @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
- */
static struct TEH_KeyStateHandle *
-get_key_state (bool management_only)
+keys_get_state (bool management_only)
{
struct TEH_KeyStateHandle *old_ksh;
struct TEH_KeyStateHandle *ksh;
@@ -2131,7 +3389,7 @@ get_key_state (bool management_only)
if ( (old_ksh->key_generation < key_generation) ||
(GNUNET_TIME_absolute_is_past (old_ksh->signature_expires.abs_time)) )
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ 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);
@@ -2149,27 +3407,58 @@ get_key_state (bool management_only)
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 = get_key_state (false);
+ 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_DenominationHash *h_denom_pub,
- struct MHD_Connection *conn,
- MHD_RESULT *mret)
+TEH_keys_denomination_by_hash (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct MHD_Connection *conn,
+ MHD_RESULT *mret)
{
struct TEH_KeyStateHandle *ksh;
@@ -2182,17 +3471,18 @@ TEH_keys_denomination_by_hash (const struct TALER_DenominationHash *h_denom_pub,
NULL);
return NULL;
}
- return TEH_keys_denomination_by_hash2 (ksh,
- h_denom_pub,
- conn,
- mret);
+
+ return TEH_keys_denomination_by_hash_from_state (ksh,
+ h_denom_pub,
+ conn,
+ mret);
}
struct TEH_DenominationKey *
-TEH_keys_denomination_by_hash2 (
- struct TEH_KeyStateHandle *ksh,
- const struct TALER_DenominationHash *h_denom_pub,
+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)
{
@@ -2212,49 +3502,217 @@ TEH_keys_denomination_by_hash2 (
}
-struct TALER_BlindedDenominationSignature
-TEH_keys_denomination_sign (const struct TALER_DenominationHash *h_denom_pub,
- const void *msg,
- size_t msg_size,
- enum TALER_ErrorCode *ec)
+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 TALER_BlindedDenominationSignature none;
struct HelperDenomination *hd;
- memset (&none,
- 0,
- sizeof (none));
ksh = TEH_keys_get_state ();
if (NULL == ksh)
{
- *ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
- return none;
+ return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
}
hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
&h_denom_pub->hash);
if (NULL == hd)
{
- *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
- return none;
+ 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;
}
- switch (hd->denom_pub.cipher)
+ for (unsigned int i = 0; i<cdds_length; i++)
{
- case TALER_DENOMINATION_RSA:
- return TALER_CRYPTO_helper_rsa_sign (ksh->helpers->dh,
- &hd->h_details.h_rsa,
- msg,
- msg_size,
- ec);
- default:
- *ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- return none;
+ 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_DenominationHash *h_denom_pub)
+TEH_keys_denomination_revoke (const struct TALER_DenominationHashP *h_denom_pub)
{
struct TEH_KeyStateHandle *ksh;
struct HelperDenomination *hd;
@@ -2272,17 +3730,23 @@ TEH_keys_denomination_revoke (const struct TALER_DenominationHash *h_denom_pub)
GNUNET_break (0);
return;
}
- switch (hd->denom_pub.cipher)
+ switch (hd->denom_pub.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_RSA:
- TALER_CRYPTO_helper_rsa_revoke (ksh->helpers->dh,
+ 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;
- default:
- GNUNET_break (0);
+ 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;
}
@@ -2312,13 +3776,15 @@ TEH_keys_exchange_sign_ (
enum TALER_ErrorCode
TEH_keys_exchange_sign2_ (
- struct TEH_KeyStateHandle *ksh,
+ 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,
@@ -2401,7 +3867,11 @@ 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;
@@ -2441,7 +3911,8 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc,
const struct KeysResponseData *krd;
ksh = TEH_keys_get_state ();
- if (NULL == ksh)
+ if ( (NULL == ksh) ||
+ (0 == ksh->krd_array_length) )
{
if ( ( (SKR_LIMIT == skr_size) &&
(rc->connection == skr_connection) ) ||
@@ -2482,6 +3953,14 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc,
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 ==
@@ -2551,92 +4030,35 @@ load_extension_data (const char *section_name,
section_name);
return GNUNET_SYSERR;
}
- if (GNUNET_OK !=
- TALER_config_get_amount (TEH_cfg,
- section_name,
- "FEE_WITHDRAW",
- &meta->fee_withdraw))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "Need amount for option `%s' in section `%s'\n",
- "FEE_WITHDRAW",
- section_name);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_config_get_amount (TEH_cfg,
- section_name,
- "FEE_DEPOSIT",
- &meta->fee_deposit))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "Need amount for option `%s' in section `%s'\n",
- "FEE_DEPOSIT",
- section_name);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_config_get_amount (TEH_cfg,
- section_name,
- "FEE_REFRESH",
- &meta->fee_refresh))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "Need amount for option `%s' in section `%s'\n",
- "FEE_REFRESH",
- section_name);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_config_get_amount (TEH_cfg,
- section_name,
- "FEE_REFUND",
- &meta->fee_refund))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "Need amount for option `%s' in section `%s'\n",
- "FEE_REFUND",
- section_name);
- return GNUNET_SYSERR;
- }
- if ( (0 != strcasecmp (TEH_currency,
- meta->value.currency)) ||
- (0 != strcasecmp (TEH_currency,
- meta->fee_withdraw.currency)) ||
- (0 != strcasecmp (TEH_currency,
- meta->fee_deposit.currency)) ||
- (0 != strcasecmp (TEH_currency,
- meta->fee_refresh.currency)) ||
- (0 != strcasecmp (TEH_currency,
- meta->fee_refund.currency)) )
+ if (0 != strcasecmp (TEH_currency,
+ meta->value.currency))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Need amounts in section `%s' to use currency `%s'\n",
+ "Need denomination value in section `%s' to use currency `%s'\n",
section_name,
TEH_currency);
return GNUNET_SYSERR;
}
- meta->age_restrictions = load_age_mask (section_name);
+ 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 (const struct TALER_DenominationHash *h_denom_pub,
+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 TEH_KeyStateHandle *ksh;
struct HelperDenomination *hd;
enum GNUNET_GenericReturnValue ok;
- ksh = get_key_state (true);
- if (NULL == ksh)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
-
hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
&h_denom_pub->hash);
if (NULL == hd)
@@ -2654,9 +4076,10 @@ TEH_keys_load_fees (const struct TALER_DenominationHash *h_denom_pub,
meta);
if (GNUNET_OK == ok)
{
- GNUNET_assert (TALER_DENOMINATION_INVALID != hd->denom_pub.cipher);
- TALER_denom_pub_deep_copy (denom_pub,
- &hd->denom_pub);
+ GNUNET_assert (GNUNET_CRYPTO_BSA_INVALID !=
+ hd->denom_pub.bsign_pub_key->cipher);
+ TALER_denom_pub_copy (denom_pub,
+ &hd->denom_pub);
}
else
{
@@ -2679,7 +4102,7 @@ TEH_keys_get_timing (const struct TALER_ExchangePublicKeyP *exchange_pub,
struct HelperSignkey *hsk;
struct GNUNET_PeerIdentity pid;
- ksh = get_key_state (true);
+ ksh = TEH_keys_get_state_for_management_only ();
if (NULL == ksh)
{
GNUNET_break (0);
@@ -2689,6 +4112,11 @@ TEH_keys_get_timing (const struct TALER_ExchangePublicKeyP *exchange_pub,
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 (
@@ -2743,7 +4171,7 @@ add_future_denomkey_cb (void *cls,
struct FutureBuilderContext *fbc = cls;
struct HelperDenomination *hd = value;
struct TEH_DenominationKey *dk;
- struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0};
dk = GNUNET_CONTAINER_multihashmap_get (fbc->ksh->denomkey_map,
h_denom_pub);
@@ -2779,14 +4207,8 @@ add_future_denomkey_cb (void *cls,
meta.expire_legal),
TALER_JSON_pack_denom_pub ("denom_pub",
&hd->denom_pub),
- TALER_JSON_pack_amount ("fee_withdraw",
- &meta.fee_withdraw),
- TALER_JSON_pack_amount ("fee_deposit",
- &meta.fee_deposit),
- TALER_JSON_pack_amount ("fee_refresh",
- &meta.fee_refresh),
- TALER_JSON_pack_amount ("fee_refund",
- &meta.fee_refund),
+ 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",
@@ -2855,7 +4277,7 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
json_t *reply;
(void) rh;
- ksh = get_key_state (true);
+ ksh = TEH_keys_get_state_for_management_only ();
if (NULL == ksh)
{
return TALER_MHD_reply_with_error (connection,
@@ -2872,8 +4294,10 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
.signkeys = json_array ()
};
- if (GNUNET_is_zero (&denom_sm_pub))
+ 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,
@@ -2886,7 +4310,6 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE,
NULL);
}
- // then a secmod helper is not yet running and we should return an MHD_HTTP_BAD_GATEWAY!
GNUNET_assert (NULL != fbc.denoms);
GNUNET_assert (NULL != fbc.signkeys);
GNUNET_CONTAINER_multihashmap_iterate (ksh->helpers->denom_keys,
@@ -2903,7 +4326,9 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
GNUNET_JSON_pack_data_auto ("master_pub",
&TEH_master_public_key),
GNUNET_JSON_pack_data_auto ("denom_secmod_public_key",
- &denom_sm_pub),
+ &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,
diff --git a/src/exchange/taler-exchange-httpd_keys.h b/src/exchange/taler-exchange-httpd_keys.h
index ce9068ec2..e526385ff 100644
--- a/src/exchange/taler-exchange-httpd_keys.h
+++ b/src/exchange/taler-exchange-httpd_keys.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ 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
@@ -49,7 +49,7 @@ struct TEH_DenominationKey
/**
* Hash code of the denomination public key.
*/
- struct TALER_DenominationHash h_denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
/**
* Meta data about the type of the denomination, such as fees and validity
@@ -83,6 +83,59 @@ struct TEH_DenominationKey
/**
+ * 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
@@ -101,6 +154,48 @@ struct TEH_KeyStateHandle;
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
@@ -115,6 +210,12 @@ TEH_check_invariants (void);
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
@@ -130,6 +231,20 @@ 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.
@@ -141,9 +256,10 @@ TEH_keys_update_states (void);
* or NULL if @a h_denom_pub could not be found
*/
struct TEH_DenominationKey *
-TEH_keys_denomination_by_hash (const struct TALER_DenominationHash *h_denom_pub,
- struct MHD_Connection *conn,
- MHD_RESULT *mret);
+TEH_keys_denomination_by_hash (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct MHD_Connection *conn,
+ MHD_RESULT *mret);
/**
@@ -160,32 +276,98 @@ TEH_keys_denomination_by_hash (const struct TALER_DenominationHash *h_denom_pub,
* or NULL if @a h_denom_pub could not be found
*/
struct TEH_DenominationKey *
-TEH_keys_denomination_by_hash2 (struct TEH_KeyStateHandle *ksh,
- const struct
- TALER_DenominationHash *h_denom_pub,
- struct MHD_Connection *conn,
- MHD_RESULT *mret);
+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);
/**
- * Request to sign @a msg using the public key corresponding to
- * @a h_denom_pub.
+ * 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 h_denom_pub hash of the public key to use to sign
- * @param msg message to sign
- * @param msg_size number of bytes in @a msg
- * @param[out] ec set to the error code (or #TALER_EC_NONE on success)
- * @return signature, the value inside the structure will be NULL on failure,
- * see @a ec for details about the failure
+ * @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
*/
-struct TALER_BlindedDenominationSignature
-TEH_keys_denomination_sign (const struct TALER_DenominationHash *h_denom_pub,
- const void *msg,
- size_t msg_size,
- enum TALER_ErrorCode *ec);
+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]);
/**
- * Revoke the public key associated with @param h_denom_pub .
+ * 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().
*
@@ -196,7 +378,8 @@ TEH_keys_denomination_sign (const struct TALER_DenominationHash *h_denom_pub,
* @param h_denom_pub hash of the public key to revoke
*/
void
-TEH_keys_denomination_revoke (const struct TALER_DenominationHash *h_denom_pub);
+TEH_keys_denomination_revoke (
+ const struct TALER_DenominationHashP *h_denom_pub);
/**
@@ -207,7 +390,7 @@ TEH_keys_finished (void);
/**
- * Resumse all suspended /keys requests, we may now have key material
+ * Resumes all suspended /keys requests, we may now have key material
* (or are shutting down).
*
* @param do_shutdown are we shutting down?
@@ -244,7 +427,7 @@ TEH_keys_exchange_sign_ (
* number of bytes of the data structure, including its header. Use
* #TEH_keys_exchange_sign() instead of calling this function directly!
*
- * @param ksh key state state to look in
+ * @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
@@ -252,7 +435,7 @@ TEH_keys_exchange_sign_ (
*/
enum TALER_ErrorCode
TEH_keys_exchange_sign2_ (
- struct TEH_KeyStateHandle *ksh,
+ void *cls,
const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
struct TALER_ExchangePublicKeyP *pub,
struct TALER_ExchangeSignatureP *sig);
@@ -364,6 +547,7 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
* 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!)
@@ -373,7 +557,8 @@ TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
* #GNUNET_SYSERR on hard errors
*/
enum GNUNET_GenericReturnValue
-TEH_keys_load_fees (const struct TALER_DenominationHash *h_denom_pub,
+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);
diff --git a/src/exchange/taler-exchange-httpd_kyc-check.c b/src/exchange/taler-exchange-httpd_kyc-check.c
index 7560d6262..362c20a2e 100644
--- a/src/exchange/taler-exchange-httpd_kyc-check.c
+++ b/src/exchange/taler-exchange-httpd_kyc-check.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -25,6 +25,7 @@
#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"
@@ -54,42 +55,93 @@ struct KycPoller
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;
/**
- * UUID being checked.
+ * Row of the requirement being checked.
*/
- uint64_t payment_target_uuid;
+ uint64_t requirement_row;
/**
- * Current KYC status.
+ * Row of KYC process being initiated.
*/
- struct TALER_EXCHANGEDB_KycStatus kyc;
+ uint64_t process_row;
/**
* Hash of the payto:// URI we are confirming to
* have finished the KYC for.
*/
- struct TALER_PaytoHash h_payto;
+ struct TALER_PaytoHashP h_payto;
/**
- * Hash of the payto:// URI that was given to us for auth.
+ * When will this request time out?
*/
- struct TALER_PaytoHash auth_h_payto;
+ struct GNUNET_TIME_Absolute timeout;
/**
- * When will this request time out?
+ * If the KYC complete, what kind of data was collected?
*/
- struct GNUNET_TIME_Absolute timeout;
+ 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;
+
};
@@ -114,6 +166,11 @@ TEH_kyc_check_cleanup ()
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;
@@ -143,11 +200,86 @@ kyp_cleanup (struct TEH_RequestContext *rc)
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
@@ -168,11 +300,53 @@ kyc_check (void *cls,
{
struct KycPoller *kyp = cls;
enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->select_kyc_status (TEH_plugin->cls,
- kyp->payment_target_uuid,
- &kyp->h_payto,
- &kyp->kyc);
+ 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)
@@ -181,9 +355,95 @@ kyc_check (void *cls,
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
- "inselect_wallet_status");
+ "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;
}
@@ -231,7 +491,7 @@ db_event_cb (void *cls,
MHD_RESULT
TEH_handler_kyc_check (
struct TEH_RequestContext *rc,
- const char *const args[])
+ const char *const args[3])
{
struct KycPoller *kyp = rc->rh_ctx;
MHD_RESULT res;
@@ -246,87 +506,62 @@ TEH_handler_kyc_check (
rc->rh_cleaner = &kyp_cleanup;
{
- unsigned long long payment_target_uuid;
+ unsigned long long requirement_row;
char dummy;
if (1 !=
sscanf (args[0],
"%llu%c",
- &payment_target_uuid,
+ &requirement_row,
&dummy))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "payment_target_uuid");
+ "requirement_row");
}
- kyp->payment_target_uuid = (uint64_t) payment_target_uuid;
+ kyp->requirement_row = (uint64_t) requirement_row;
}
- {
- const char *ts;
- ts = MHD_lookup_connection_value (rc->connection,
- MHD_GET_ARGUMENT_KIND,
- "timeout_ms");
- if (NULL != ts)
- {
- char dummy;
- unsigned long long tms;
-
- if (1 !=
- sscanf (ts,
- "%llu%c",
- &tms,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "timeout_ms");
- }
- kyp->timeout = GNUNET_TIME_relative_to_absolute (
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
- tms));
- }
- }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[1],
+ strlen (args[1]),
+ &kyp->h_payto,
+ sizeof (kyp->h_payto)))
{
- const char *hps;
-
- hps = MHD_lookup_connection_value (rc->connection,
- MHD_GET_ARGUMENT_KIND,
+ 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 == hps)
- {
- 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 (hps,
- strlen (hps),
- &kyp->auth_h_payto,
- sizeof (kyp->auth_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 (TEH_KYC_NONE == TEH_kyc_config.mode)
- return TALER_MHD_reply_static (
+ 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_NO_CONTENT,
- NULL,
- NULL,
- 0);
+ 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) )
@@ -334,7 +569,7 @@ TEH_handler_kyc_check (
struct TALER_KycCompletedEventP rep = {
.header.size = htons (sizeof (rep)),
.header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
- .h_payto = kyp->auth_h_payto
+ .h_payto = kyp->h_payto
};
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -350,29 +585,78 @@ TEH_handler_kyc_check (
now = GNUNET_TIME_timestamp_get ();
ret = TEH_DB_run_transaction (rc->connection,
"kyc check",
- TEH_MT_OTHER,
+ TEH_MT_REQUEST_OTHER,
&res,
&kyc_check,
kyp);
if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Transaction failed.\n");
return res;
- if (0 !=
- GNUNET_memcmp (&kyp->h_payto,
- &kyp->auth_h_payto))
+ }
+ /* KYC plugin generated reply? */
+ if (NULL != kyp->kyc_url)
{
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_UNAUTHORIZED,
- TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED,
- "h_payto");
+ 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 ( (! kyp->kyc.ok) &&
+ 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);
@@ -380,53 +664,27 @@ TEH_handler_kyc_check (
return MHD_YES;
}
- /* KYC failed? */
- if (! kyp->kyc.ok)
+ if (TALER_EC_NONE != kyp->ec)
{
- char *url;
- char *redirect_uri;
- char *redirect_uri_encoded;
-
- GNUNET_assert (TEH_KYC_OAUTH2 == TEH_kyc_config.mode);
- GNUNET_asprintf (&redirect_uri,
- "%s/kyc-proof/%llu",
- TEH_base_url,
- (unsigned long long) kyp->payment_target_uuid);
- redirect_uri_encoded = TALER_urlencode (redirect_uri);
- GNUNET_free (redirect_uri);
- GNUNET_asprintf (&url,
- "%s/login?client_id=%s&redirect_uri=%s",
- TEH_kyc_config.details.oauth2.url,
- TEH_kyc_config.details.oauth2.client_id,
- redirect_uri_encoded);
- GNUNET_free (redirect_uri_encoded);
-
- res = TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_ACCEPTED,
- GNUNET_JSON_pack_string ("kyc_url",
- url));
- GNUNET_free (url);
- return res;
+ return TALER_MHD_reply_with_ec (rc->connection,
+ kyp->ec,
+ kyp->hint);
}
- /* KYC succeeded! */
+ /* KYC must have succeeded! */
{
struct TALER_ExchangePublicKeyP pub;
struct TALER_ExchangeSignatureP sig;
- struct TALER_ExchangeAccountSetupSuccessPS as = {
- .purpose.purpose = htonl (
- TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS),
- .purpose.size = htonl (sizeof (as)),
- .h_payto = kyp->h_payto,
- .timestamp = GNUNET_TIME_timestamp_hton (now)
- };
enum TALER_ErrorCode ec;
if (TALER_EC_NONE !=
- (ec = TEH_keys_exchange_sign (&as,
- &pub,
- &sig)))
+ (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,
@@ -439,6 +697,10 @@ TEH_handler_kyc_check (
&sig),
GNUNET_JSON_pack_data_auto ("exchange_pub",
&pub),
+ GNUNET_JSON_pack_uint64 ("aml_status",
+ kyp->aml_status),
+ GNUNET_JSON_pack_object_incref ("kyc_details",
+ kyp->kyc_details),
GNUNET_JSON_pack_timestamp ("now",
now));
}
diff --git a/src/exchange/taler-exchange-httpd_kyc-check.h b/src/exchange/taler-exchange-httpd_kyc-check.h
index 52fe5f16b..f1f2c9e7d 100644
--- a/src/exchange/taler-exchange-httpd_kyc-check.h
+++ b/src/exchange/taler-exchange-httpd_kyc-check.h
@@ -29,8 +29,8 @@
* Handle a "/kyc-check" request. Checks the KYC
* status of the given account and returns it.
*
- * @param connection request to handle
- * @param args one argument with the payment_target_uuid
+ * @param rc details about the request to handle
+ * @param args one argument with the legitimization_uuid
* @return MHD result code
*/
MHD_RESULT
diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.c b/src/exchange/taler-exchange-httpd_kyc-proof.c
index 24ddfc74d..bad377a2a 100644
--- a/src/exchange/taler-exchange-httpd_kyc-proof.c
+++ b/src/exchange/taler-exchange-httpd_kyc-proof.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -23,9 +23,12 @@
#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_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"
@@ -52,35 +55,41 @@ struct KycProofContext
struct TEH_RequestContext *rc;
/**
- * Handle for the OAuth 2.0 CURL request.
+ * Proof logic to run.
*/
- struct GNUNET_CURL_Job *job;
+ struct TALER_KYCLOGIC_Plugin *logic;
/**
- * OAuth 2.0 authorization code.
+ * Configuration for @a logic.
*/
- const char *authorization_code;
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
/**
- * OAuth 2.0 token URL we are using for the
- * request.
+ * Asynchronous operation with the proof system.
*/
- char *token_url;
+ struct TALER_KYCLOGIC_ProofHandle *ph;
/**
- * Body of the POST request.
+ * KYC AML trigger operation.
*/
- char *post_body;
+ struct TEH_KycAmlTrigger *kat;
/**
- * User ID extracted from the OAuth 2.0 service, or NULL.
+ * Process information about the user for the plugin from the database, can
+ * be NULL.
*/
- char *id;
+ char *provider_user_id;
/**
- * Payment target this is about.
+ * Process information about the legitimization process for the plugin from the
+ * database, can be NULL.
*/
- unsigned long long payment_target_uuid;
+ char *provider_legitimization_id;
+
+ /**
+ * Hash of payment target URI this is about.
+ */
+ struct TALER_PaytoHashP h_payto;
/**
* HTTP response to return.
@@ -88,16 +97,24 @@ struct KycProofContext
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;
/**
- * #GNUNET_YES if we are suspended,
- * #GNUNET_NO if not.
- * #GNUNET_SYSERR if we had some error.
+ * True if we are suspended,
*/
- enum GNUNET_GenericReturnValue suspended;
+ bool suspended;
};
@@ -122,7 +139,7 @@ static void
kpc_resume (struct KycProofContext *kpc)
{
GNUNET_assert (GNUNET_YES == kpc->suspended);
- kpc->suspended = GNUNET_NO;
+ kpc->suspended = false;
GNUNET_CONTAINER_DLL_remove (kpc_head,
kpc_tail,
kpc);
@@ -138,10 +155,10 @@ TEH_kyc_proof_cleanup (void)
while (NULL != (kpc = kpc_head))
{
- if (NULL != kpc->job)
+ if (NULL != kpc->ph)
{
- GNUNET_CURL_job_cancel (kpc->job);
- kpc->job = NULL;
+ kpc->logic->proof_cancel (kpc->ph);
+ kpc->ph = NULL;
}
kpc_resume (kpc);
}
@@ -149,348 +166,232 @@ TEH_kyc_proof_cleanup (void)
/**
- * Function implementing database transaction to check proof'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.
+ * Function called after the KYC-AML trigger is done.
*
- * @param cls closure with a `struct KycProofContext *`
- * @param connection MHD proof which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
+ * @param cls closure
+ * @param http_status final HTTP status to return
+ * @param[in] response final HTTP ro return
*/
-static enum GNUNET_DB_QueryStatus
-persist_kyc_ok (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
+static void
+proof_finish (
+ void *cls,
+ unsigned int http_status,
+ struct MHD_Response *response)
{
struct KycProofContext *kpc = cls;
- enum GNUNET_DB_QueryStatus qs;
- qs = TEH_plugin->set_kyc_ok (TEH_plugin->cls,
- kpc->payment_target_uuid,
- kpc->id);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_ec (connection,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "set_kyc_ok");
- }
- return qs;
+ kpc->kat = NULL;
+ kpc->response_code = http_status;
+ kpc->response = response;
+ kpc_resume (kpc);
}
/**
- * The request for @a kpc failed. We may have gotten a useful error
- * message in @a j. Generate a failure response.
+ * Generate HTML error for @a connection using @a template.
*
- * @param[in,out] kpc request that failed
- * @param j reply from the server (or NULL)
+ * @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
*/
-static void
-handle_error (struct KycProofContext *kpc,
- const json_t *j)
+struct MHD_Response *
+make_html_error (struct MHD_Connection *connection,
+ const char *template,
+ unsigned int *http_status,
+ enum TALER_ErrorCode ec,
+ const char *message)
{
- 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 ()
- };
-
- {
- enum GNUNET_GenericReturnValue res;
- const char *emsg;
- unsigned int line;
-
- res = GNUNET_JSON_parse (j,
- spec,
- &emsg,
- &line);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- kpc->response
- = TALER_MHD_make_error (
- TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
- "Unexpected response from KYC gateway");
- kpc->response_code
- = MHD_HTTP_BAD_GATEWAY;
- return;
- }
- }
- /* case TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_AUTHORZATION_FAILED,
- we MAY want to in the future look at the requested content type
- and possibly respond in JSON if indicated. */
- {
- char *reply;
-
- GNUNET_asprintf (&reply,
- "<html><head><title>%s</title></head><body><h1>%s</h1><p>%s</p></body></html>",
- msg,
- msg,
- desc);
- kpc->response
- = MHD_create_response_from_buffer (strlen (reply),
- reply,
- MHD_RESPMEM_MUST_COPY);
- GNUNET_assert (NULL != kpc->response);
- GNUNET_free (reply);
- }
- kpc->response_code = MHD_HTTP_FORBIDDEN;
+ 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;
}
/**
- * The request for @a kpc succeeded (presumably).
- * Parse the user ID and store it in @a kpc (if possible).
+ * Respond with an HTML message on the given @a rc.
*
- * @param[in,out] kpc request that succeeded
- * @param j reply from the server
+ * @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 void
-parse_success_reply (struct KycProofContext *kpc,
- const json_t *j)
+static MHD_RESULT
+respond_html_ec (struct TEH_RequestContext *rc,
+ unsigned int http_status,
+ const char *template,
+ enum TALER_ErrorCode ec,
+ const char *message)
{
- const char *state;
- json_t *data;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("status",
- &state),
- GNUNET_JSON_spec_json ("data",
- &data),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
- const char *emsg;
- unsigned int line;
-
- res = GNUNET_JSON_parse (j,
- spec,
- &emsg,
- &line);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- kpc->response
- = TALER_MHD_make_error (
- TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
- "Unexpected response from KYC gateway");
- kpc->response_code
- = MHD_HTTP_BAD_GATEWAY;
- return;
- }
- if (0 != strcasecmp (state,
- "success"))
- {
- GNUNET_break_op (0);
- handle_error (kpc,
- j);
- return;
- }
- {
- const char *id;
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_string ("id",
- &id),
- GNUNET_JSON_spec_end ()
- };
-
- res = GNUNET_JSON_parse (data,
- ispec,
- &emsg,
- &line);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- kpc->response
- = TALER_MHD_make_error (
- TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
- "Unexpected response from KYC gateway");
- kpc->response_code
- = MHD_HTTP_BAD_GATEWAY;
- return;
- }
- kpc->id = GNUNET_strdup (id);
- }
+ 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;
}
/**
- * After we are done with the CURL interaction we
- * need to update our database state with the information
- * retrieved.
+ * Function called with the result of a proof check operation.
*
- * @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_fetch_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct KycProofContext *kpc = cls;
- const json_t *j = response;
-
- kpc->job = NULL;
- switch (response_code)
- {
- case MHD_HTTP_OK:
- parse_success_reply (kpc,
- j);
- break;
- default:
- handle_error (kpc,
- j);
- break;
- }
- kpc_resume (kpc);
-}
-
-
-/**
- * After we are done with the CURL interaction we
- * need to fetch the user's account details.
+ * Note that the "decref" for the @a response
+ * will be done by the callee and MUST NOT be done by the plugin.
*
- * @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
+ * @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
-handle_curl_login_finished (void *cls,
- long response_code,
- const void *response)
+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;
- const json_t *j = response;
+ struct TEH_RequestContext *rc = kpc->rc;
+ struct GNUNET_AsyncScopeSave old_scope;
- kpc->job = NULL;
- switch (response_code)
+ kpc->ph = NULL;
+ GNUNET_async_scope_enter (&rc->async_scope_id,
+ &old_scope);
+ switch (status)
{
- case MHD_HTTP_OK:
+ 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)
{
- const char *access_token;
- const char *token_type;
- uint64_t expires_in_s;
- const char *refresh_token;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("access_token",
- &access_token),
- GNUNET_JSON_spec_string ("token_type",
- &token_type),
- GNUNET_JSON_spec_uint64 ("expires_in",
- &expires_in_s),
- GNUNET_JSON_spec_string ("refresh_token",
- &refresh_token),
- 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)
- {
- GNUNET_break_op (0);
- kpc->response
- = TALER_MHD_make_error (
- TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
- "Unexpected response from KYC gateway");
- kpc->response_code
- = MHD_HTTP_BAD_GATEWAY;
- break;
- }
- }
- if (0 != strcasecmp (token_type,
- "bearer"))
- {
- GNUNET_break_op (0);
- kpc->response
- = TALER_MHD_make_error (
- TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
- "Unexpected token type in response from KYC gateway");
- kpc->response_code
- = MHD_HTTP_BAD_GATEWAY;
- 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,
- ';')) )
- {
- GNUNET_break_op (0);
- kpc->response
- = TALER_MHD_make_error (
- TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
- "Illegal character in access token");
- kpc->response_code
- = MHD_HTTP_BAD_GATEWAY;
- break;
- }
-
- eh = curl_easy_init ();
- if (NULL == eh)
- {
- GNUNET_break_op (0);
- kpc->response
- = TALER_MHD_make_error (
- TALER_EC_GENERIC_ALLOCATION_FAILURE,
- "curl_easy_init");
- kpc->response_code
- = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- }
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_URL,
- TEH_kyc_config.details.oauth2.info_url));
+ 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))
{
- char *hdr;
- struct curl_slist *slist;
-
- GNUNET_asprintf (&hdr,
- "%s: Bearer %s",
- MHD_HTTP_HEADER_AUTHORIZATION,
- access_token);
- slist = curl_slist_append (NULL,
- hdr);
- kpc->job = GNUNET_CURL_job_add2 (TEH_curl_ctx,
- eh,
- slist,
- &handle_curl_fetch_finished,
- kpc);
- curl_slist_free_all (slist);
- GNUNET_free (hdr);
+ 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");
}
- return;
}
+ break;
default:
- handle_error (kpc,
- j);
+ 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;
}
- kpc_resume (kpc);
+ 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);
}
@@ -504,19 +405,23 @@ clean_kpc (struct TEH_RequestContext *rc)
{
struct KycProofContext *kpc = rc->rh_ctx;
- if (NULL != kpc->job)
+ if (NULL != kpc->ph)
+ {
+ kpc->logic->proof_cancel (kpc->ph);
+ kpc->ph = NULL;
+ }
+ if (NULL != kpc->kat)
{
- GNUNET_CURL_job_cancel (kpc->job);
- kpc->job = NULL;
+ TEH_kyc_finished_cancel (kpc->kat);
+ kpc->kat = NULL;
}
if (NULL != kpc->response)
{
MHD_destroy_response (kpc->response);
kpc->response = NULL;
}
- GNUNET_free (kpc->post_body);
- GNUNET_free (kpc->token_url);
- GNUNET_free (kpc->id);
+ GNUNET_free (kpc->provider_user_id);
+ GNUNET_free (kpc->provider_legitimization_id);
GNUNET_free (kpc);
}
@@ -524,191 +429,137 @@ clean_kpc (struct TEH_RequestContext *rc)
MHD_RESULT
TEH_handler_kyc_proof (
struct TEH_RequestContext *rc,
- const char *const args[])
+ const char *const args[1])
{
struct KycProofContext *kpc = rc->rh_ctx;
+ const char *provider_section_or_logic = args[0];
if (NULL == kpc)
- { /* first time */
- char dummy;
-
+ {
+ /* 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;
-
- if (1 !=
- sscanf (args[0],
- "%llu%c",
- &kpc->payment_target_uuid,
- &dummy))
+ 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 TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "payment_target_uuid");
+ return respond_html_ec (rc,
+ MHD_HTTP_NOT_FOUND,
+ "kyc-proof-target-unknown",
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
+ provider_section_or_logic);
}
- kpc->authorization_code
- = MHD_lookup_connection_value (rc->connection,
- MHD_GET_ARGUMENT_KIND,
- "code");
- if (NULL == kpc->authorization_code)
+ if (NULL != kpc->provider_section)
{
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "code");
- }
- if (TEH_KYC_NONE == TEH_kyc_config.mode)
- return TALER_MHD_reply_static (
- rc->connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute expiration;
- {
- CURL *eh;
+ 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");
+ }
- eh = curl_easy_init ();
- if (NULL == eh)
+ 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)
{
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_ALLOCATION_FAILURE,
- "curl_easy_init");
+ 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;
}
- GNUNET_asprintf (&kpc->token_url,
- "%stoken",
- TEH_kyc_config.details.oauth2.url);
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_URL,
- kpc->token_url));
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_POST,
- 1));
+ if (GNUNET_TIME_absolute_is_future (expiration))
{
- char *client_id;
- char *redirect_uri;
- char *client_secret;
- char *authorization_code;
-
- client_id = curl_easy_escape (eh,
- TEH_kyc_config.details.oauth2.client_id,
- 0);
- GNUNET_assert (NULL != client_id);
- {
- char *request_uri;
-
- GNUNET_asprintf (&request_uri,
- "%slogin?client_id=%s",
- TEH_kyc_config.details.oauth2.url,
- TEH_kyc_config.details.oauth2.client_id);
- redirect_uri = curl_easy_escape (eh,
- request_uri,
- 0);
- GNUNET_free (request_uri);
- }
- GNUNET_assert (NULL != redirect_uri);
- client_secret = curl_easy_escape (eh,
- TEH_kyc_config.details.oauth2.
- client_secret,
- 0);
- GNUNET_assert (NULL != client_secret);
- authorization_code = curl_easy_escape (eh,
- kpc->authorization_code,
- 0);
- GNUNET_assert (NULL != authorization_code);
- GNUNET_asprintf (&kpc->post_body,
- "client_id=%s&redirect_uri=%s&client_secret=%s&code=%s&grant_type=authorization_code",
- client_id,
- redirect_uri,
- client_secret,
- authorization_code);
- curl_free (authorization_code);
- curl_free (client_secret);
- curl_free (redirect_uri);
- curl_free (client_id);
+ /* KYC not required */
+ return respond_html_ec (rc,
+ MHD_HTTP_OK,
+ "kyc-proof-already-done",
+ TALER_EC_NONE,
+ NULL);
}
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_POSTFIELDS,
- kpc->post_body));
- 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));
-
- kpc->job = GNUNET_CURL_job_add (TEH_curl_ctx,
- eh,
- &handle_curl_login_finished,
- kpc);
- kpc->suspended = GNUNET_YES;
- GNUNET_CONTAINER_DLL_insert (kpc_head,
- kpc_tail,
- kpc);
- MHD_suspend_connection (rc->connection);
- return MHD_YES;
}
- }
+ 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");
+ }
- if (NULL != kpc->response)
- {
- /* handle _failed_ resumed cases */
- return MHD_queue_response (rc->connection,
- kpc->response_code,
- kpc->response);
- }
- /* _successfully_ resumed case */
- {
- MHD_RESULT res;
- enum GNUNET_GenericReturnValue ret;
-
- ret = TEH_DB_run_transaction (kpc->rc->connection,
- "check proof kyc",
- TEH_MT_OTHER,
- &res,
- &persist_kyc_ok,
- kpc);
- if (GNUNET_SYSERR == ret)
- return res;
+ kpc->suspended = true;
+ GNUNET_CONTAINER_DLL_insert (kpc_head,
+ kpc_tail,
+ kpc);
+ MHD_suspend_connection (rc->connection);
+ return MHD_YES;
}
+ if (NULL == kpc->response)
{
- struct MHD_Response *response;
- MHD_RESULT res;
-
- response = MHD_create_response_from_buffer (0,
- "",
- MHD_RESPMEM_PERSISTENT);
- if (NULL == response)
- {
- GNUNET_break (0);
- return MHD_NO;
- }
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (
- response,
- MHD_HTTP_HEADER_LOCATION,
- TEH_kyc_config.details.oauth2.post_kyc_redirect_url));
- res = MHD_queue_response (rc->connection,
- MHD_HTTP_SEE_OTHER,
- response);
- MHD_destroy_response (response);
- return res;
+ 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);
}
diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.h b/src/exchange/taler-exchange-httpd_kyc-proof.h
index c075b2424..d40ea90a9 100644
--- a/src/exchange/taler-exchange-httpd_kyc-proof.h
+++ b/src/exchange/taler-exchange-httpd_kyc-proof.h
@@ -37,13 +37,13 @@ TEH_kyc_proof_cleanup (void);
* Handle a "/kyc-proof" request.
*
* @param rc request to handle
- * @param args one argument with the payment_target_uuid
+ * @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[]);
+ 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
index 4062f9305..21d07422d 100644
--- a/src/exchange/taler-exchange-httpd_kyc-wallet.c
+++ b/src/exchange/taler-exchange-httpd_kyc-wallet.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021, 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -26,6 +26,7 @@
#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"
@@ -38,16 +39,60 @@ 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;
/**
- * Current KYC status.
+ * 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
@@ -69,9 +114,14 @@ wallet_kyc_check (void *cls,
struct KycRequestContext *krc = cls;
enum GNUNET_DB_QueryStatus qs;
- qs = TEH_plugin->inselect_wallet_kyc_status (TEH_plugin->cls,
- &krc->reserve_pub,
- &krc->kyc);
+ 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)
@@ -80,9 +130,40 @@ wallet_kyc_check (void *cls,
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
- "inselect_wallet_status");
+ "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;
}
@@ -100,14 +181,13 @@ TEH_handler_kyc_wallet (
&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;
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose = {
- .size = htonl (sizeof (purpose)),
- .purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_SETUP)
- };
(void) args;
ret = TALER_MHD_parse_json_data (rc->connection,
@@ -118,11 +198,11 @@ TEH_handler_kyc_wallet (
if (GNUNET_NO == ret)
return MHD_YES; /* failure */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_ACCOUNT_SETUP,
- &purpose,
- &reserve_sig.eddsa_signature,
- &krc.reserve_pub.eddsa_pub))
+ TALER_wallet_account_setup_verify (&krc.reserve_pub,
+ &krc.balance,
+ &reserve_sig))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (
@@ -131,26 +211,41 @@ TEH_handler_kyc_wallet (
TALER_EC_EXCHANGE_KYC_WALLET_SIGNATURE_INVALID,
NULL);
}
- if (TEH_KYC_NONE == TEH_kyc_config.mode)
- return TALER_MHD_reply_static (
- rc->connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
+ {
+ 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_OTHER,
+ TEH_MT_REQUEST_OTHER,
&res,
&wallet_kyc_check,
&krc);
if (GNUNET_SYSERR == ret)
return res;
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_uint64 ("payment_target_uuid",
- krc.kyc.payment_target_uuid));
+ 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);
}
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 d3c0d6a5a..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
@@ -39,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.
@@ -86,8 +86,18 @@ handle_link_data (void *cls,
&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));
+ &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,
@@ -143,7 +153,7 @@ link_transaction (void *cls,
enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->get_link_data (TEH_plugin->cls,
- &ctx->coin_pub,
+ ctx->coin_pub,
&handle_link_data,
ctx);
if (NULL == ctx->mlist)
@@ -168,32 +178,19 @@ link_transaction (void *cls,
MHD_RESULT
TEH_handler_link (struct TEH_RequestContext *rc,
- const char *const args[2])
+ const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
- struct HTD_Context ctx;
+ struct HTD_Context ctx = {
+ .coin_pub = coin_pub
+ };
MHD_RESULT mhd_ret;
- 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 (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_COINS_INVALID_COIN_PUB,
- args[0]);
- }
ctx.mlist = json_array ();
GNUNET_assert (NULL != ctx.mlist);
if (GNUNET_OK !=
TEH_DB_run_transaction (rc->connection,
"run link",
- TEH_MT_OTHER,
+ TEH_MT_REQUEST_OTHER,
&mhd_ret,
&link_transaction,
&ctx))
diff --git a/src/exchange/taler-exchange-httpd_link.h b/src/exchange/taler-exchange-httpd_link.h
index 01679e877..255c0ca57 100644
--- a/src/exchange/taler-exchange-httpd_link.h
+++ b/src/exchange/taler-exchange-httpd_link.h
@@ -32,12 +32,12 @@
* Handle a "/coins/$COIN_PUB/link" request.
*
* @param rc request context
- * @param args array of additional options (length: 2, first is the coin_pub, second must be "link")
+ * @param coin_pub the coin public key
* @return MHD result code
*/
MHD_RESULT
TEH_handler_link (struct TEH_RequestContext *rc,
- const char *const args[2]);
+ 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
index f0d922e64..2fc1fe8db 100644
--- a/src/exchange/taler-exchange-httpd_management.h
+++ b/src/exchange/taler-exchange-httpd_management.h
@@ -64,7 +64,7 @@ TEH_handler_management_auditors_AP_disable (
MHD_RESULT
TEH_handler_management_denominations_HDP_revoke (
struct MHD_Connection *connection,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const json_t *root);
@@ -136,6 +136,19 @@ TEH_handler_management_post_wire_fees (
/**
+ * 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
@@ -149,6 +162,45 @@ TEH_handler_management_post_extensions (
/**
+ * 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
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
index 6096cc98b..7e0593534 100644
--- a/src/exchange/taler-exchange-httpd_management_auditors.c
+++ b/src/exchange/taler-exchange-httpd_management_auditors.c
@@ -153,7 +153,7 @@ TEH_handler_management_auditors (
&aac.master_sig),
GNUNET_JSON_spec_fixed_auto ("auditor_pub",
&aac.auditor_pub),
- GNUNET_JSON_spec_string ("auditor_url",
+ TALER_JSON_spec_web_url ("auditor_url",
&aac.auditor_url),
GNUNET_JSON_spec_string ("auditor_name",
&aac.auditor_name),
@@ -189,7 +189,7 @@ TEH_handler_management_auditors (
ret = TEH_DB_run_transaction (connection,
"add auditor",
- TEH_MT_OTHER,
+ TEH_MT_REQUEST_OTHER,
&res,
&add_auditor,
&aac);
diff --git a/src/exchange/taler-exchange-httpd_management_auditors_AP_disable.c b/src/exchange/taler-exchange-httpd_management_auditors_AP_disable.c
index 5ae0cbd07..07e2933bb 100644
--- a/src/exchange/taler-exchange-httpd_management_auditors_AP_disable.c
+++ b/src/exchange/taler-exchange-httpd_management_auditors_AP_disable.c
@@ -178,7 +178,7 @@ TEH_handler_management_auditors_AP_disable (
ret = TEH_DB_run_transaction (connection,
"del auditor",
- TEH_MT_OTHER,
+ TEH_MT_REQUEST_OTHER,
&res,
&del_auditor,
&dac);
diff --git a/src/exchange/taler-exchange-httpd_management_denominations_HDP_revoke.c b/src/exchange/taler-exchange-httpd_management_denominations_HDP_revoke.c
index a8acf2f7a..32da72fbd 100644
--- a/src/exchange/taler-exchange-httpd_management_denominations_HDP_revoke.c
+++ b/src/exchange/taler-exchange-httpd_management_denominations_HDP_revoke.c
@@ -34,7 +34,7 @@
MHD_RESULT
TEH_handler_management_denominations_HDP_revoke (
struct MHD_Connection *connection,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const json_t *root)
{
struct TALER_MasterSignatureP master_sig;
@@ -56,6 +56,7 @@ TEH_handler_management_denominations_HDP_revoke (
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,
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
index 96b855c3c..3b24bace7 100644
--- a/src/exchange/taler-exchange-httpd_management_extensions.c
+++ b/src/exchange/taler-exchange-httpd_management_extensions.c
@@ -31,7 +31,6 @@
#include "taler_extensions.h"
#include "taler_dbevents.h"
-
/**
* Extension carries the necessary data for a particular extension.
*
@@ -39,7 +38,7 @@
struct Extension
{
enum TALER_Extension_Type type;
- json_t *config;
+ json_t *manifest;
};
/**
@@ -49,43 +48,11 @@ struct SetExtensionsContext
{
uint32_t num_extensions;
struct Extension *extensions;
- struct TALER_MasterSignatureP *extensions_sigs;
+ struct TALER_MasterSignatureP extensions_sig;
};
-
-/**
- * @brief verifies the signature a configuration with the offline master key.
- *
- * @param config configuration of an extension given as JSON object
- * @param master_priv offline master public key of the exchange
- * @param[out] master_sig signature
- * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
- */
-static enum GNUNET_GenericReturnValue
-config_verify (
- const json_t *config,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_MasterSignatureP *master_sig
- )
-{
- enum GNUNET_GenericReturnValue ret;
- struct TALER_ExtensionConfigHash h_config;
-
- ret = TALER_extension_config_hash (config, &h_config);
- if (GNUNET_OK != ret)
- {
- GNUNET_break (0);
- return ret;
- }
-
- return TALER_exchange_offline_extension_config_hash_verify (h_config,
- master_pub,
- master_sig);
-}
-
-
/**
- * Function implementing database transaction to set the configuration of
+ * 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.
@@ -107,25 +74,24 @@ set_extensions (void *cls,
{
struct SetExtensionsContext *sec = cls;
- /* save the configurations of all extensions */
+ /* save the manifests of all extensions */
for (uint32_t i = 0; i<sec->num_extensions; i++)
{
struct Extension *ext = &sec->extensions[i];
- struct TALER_MasterSignatureP *sig = &sec->extensions_sigs[i];
+ const struct TALER_Extension *taler_ext;
enum GNUNET_DB_QueryStatus qs;
- char *config;
+ char *manifest;
- /* Sanity check.
- * TODO: replace with general API to retrieve the extension-handler
- */
- if (0 > ext->type || TALER_Extension_Max <= ext->type)
+ 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;
}
- config = json_dumps (ext->config, JSON_COMPACT | JSON_SORT_KEYS);
- if (NULL == config)
+ manifest = json_dumps (ext->manifest, JSON_COMPACT | JSON_SORT_KEYS);
+ if (NULL == manifest)
{
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
@@ -135,11 +101,12 @@ set_extensions (void *cls,
return GNUNET_DB_STATUS_HARD_ERROR;
}
- qs = TEH_plugin->set_extension_config (
+ qs = TEH_plugin->set_extension_manifest (
TEH_plugin->cls,
- TEH_extensions[ext->type]->name,
- config,
- sig);
+ taler_ext->name,
+ manifest);
+
+ free (manifest);
if (qs < 0)
{
@@ -154,41 +121,96 @@ set_extensions (void *cls,
/* Success, trigger event */
{
- enum TALER_Extension_Type *type = &sec->extensions[i].type;
+ 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,
- type,
- sizeof(*type));
+ &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};
- json_t *extensions;
- json_t *extensions_sigs;
struct GNUNET_JSON_Specification top_spec[] = {
- GNUNET_JSON_spec_json ("extensions",
- &extensions),
- GNUNET_JSON_spec_json ("extensions_sigs",
- &extensions_sigs),
+ GNUNET_JSON_spec_object_const ("extensions",
+ &extensions),
+ GNUNET_JSON_spec_fixed_auto ("extensions_sig",
+ &sec.extensions_sig),
GNUNET_JSON_spec_end ()
};
- MHD_RESULT ret;
- // Parse the top level json structure
+ /* Parse the top level json structure */
{
enum GNUNET_GenericReturnValue res;
@@ -201,159 +223,52 @@ TEH_handler_management_post_extensions (
return MHD_YES; /* failure */
}
- // Ensure we have two arrays of the same size
- if (! (json_is_array (extensions) &&
- json_is_array (extensions_sigs)) )
+ /* Verify the signature */
{
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (top_spec);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "array expected for extensions and extensions_sigs");
- }
-
- sec.num_extensions = json_array_size (extensions_sigs);
- if (json_array_size (extensions) != sec.num_extensions)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (top_spec);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "arrays extensions and extensions_sigs are not of the same size");
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received /management/extensions\n");
-
- sec.extensions = GNUNET_new_array (sec.num_extensions,
- struct Extension);
- sec.extensions_sigs = GNUNET_new_array (sec.num_extensions,
- struct TALER_MasterSignatureP);
-
- // Now parse individual extensions and signatures from those arrays.
- for (unsigned int i = 0; i<sec.num_extensions; i++)
- {
- // 1. parse the extension out of the json
- enum GNUNET_GenericReturnValue res;
- const struct TALER_Extension *extension;
- const char *name;
- struct GNUNET_JSON_Specification ext_spec[] = {
- GNUNET_JSON_spec_string ("extension",
- &name),
- GNUNET_JSON_spec_json ("config",
- &sec.extensions[i].config),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_array (connection,
- extensions,
- ext_spec,
- i,
- -1);
- if (GNUNET_SYSERR == res)
- {
- ret = MHD_NO; /* hard failure */
- goto CLEANUP;
- }
- if (GNUNET_NO == res)
- {
- ret = MHD_YES;
- goto CLEANUP;
- }
-
- /* 2. Make sure name refers to a supported extension */
- if (GNUNET_OK != TALER_extension_get_by_name (name,
- (const struct
- TALER_Extension **)
- TEH_extensions,
- &extension))
- {
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "invalid extension type");
- goto CLEANUP;
- }
-
- sec.extensions[i].type = extension->type;
-
- /* 3. Extract the signature out of the json array */
- {
- enum GNUNET_GenericReturnValue res;
- struct GNUNET_JSON_Specification sig_spec[] = {
- GNUNET_JSON_spec_fixed_auto (NULL,
- &sec.extensions_sigs[i]),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_array (connection,
- extensions_sigs,
- sig_spec,
- i,
- -1);
- if (GNUNET_SYSERR == res)
- {
- ret = MHD_NO; /* hard failure */
- goto CLEANUP;
- }
- if (GNUNET_NO == res)
- {
- ret = MHD_YES;
- goto CLEANUP;
- }
- }
-
- /* 4. Verify the signature of the config */
- if (GNUNET_OK != config_verify (
- sec.extensions[i].config,
+ 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_sigs[i]))
+ &sec.extensions_sig))
{
- ret = TALER_MHD_reply_with_error (
+ return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "invalid signature for extension");
- goto CLEANUP;
- }
-
- /* 5. Make sure the config is sound */
- if (GNUNET_OK != extension->test_config (sec.extensions[i].config))
- {
- GNUNET_JSON_parse_free (ext_spec);
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "invalid configuration for extension");
- goto CLEANUP;
-
+ "invalid signuture");
}
+ }
- /* We have a validly signed JSON object for the extension.
- * Increment its refcount and free the parser for the extension.
- */
- json_incref (sec.extensions[i].config);
- GNUNET_JSON_parse_free (ext_spec);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received /management/extensions\n");
- } /* for-loop */
+ /* 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
+ /* now run the transaction to persist the configurations */
{
enum GNUNET_GenericReturnValue res;
res = TEH_DB_run_transaction (connection,
"set extensions",
- TEH_MT_OTHER,
+ TEH_MT_REQUEST_OTHER,
&ret,
&set_extensions,
&sec);
@@ -372,14 +287,12 @@ TEH_handler_management_post_extensions (
CLEANUP:
for (unsigned int i = 0; i < sec.num_extensions; i++)
{
- if (NULL != sec.extensions[i].config)
+ if (NULL != sec.extensions[i].manifest)
{
- json_decref (sec.extensions[i].config);
+ json_decref (sec.extensions[i].manifest);
}
}
GNUNET_free (sec.extensions);
- GNUNET_free (sec.extensions_sigs);
- GNUNET_JSON_parse_free (top_spec);
return ret;
}
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
index f0c3f1f39..f91f24c41 100644
--- a/src/exchange/taler-exchange-httpd_management_post_keys.c
+++ b/src/exchange/taler-exchange-httpd_management_post_keys.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020, 2021 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -40,13 +40,23 @@ struct DenomSig
/**
* Hash of a denomination public key.
*/
- struct TALER_DenominationHash h_denom_pub;
+ 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;
+
};
@@ -65,6 +75,11 @@ struct SigningSig
*/
struct TALER_MasterSignatureP master_sig;
+ /**
+ * Our meta data on this key.
+ */
+ struct TALER_EXCHANGEDB_SignkeyMetaData meta;
+
};
@@ -85,6 +100,11 @@ struct AddKeysContext
struct SigningSig *s_sigs;
/**
+ * Our key state.
+ */
+ struct TEH_KeyStateHandle *ksh;
+
+ /**
* Length of the d_sigs array.
*/
unsigned int nd_sigs;
@@ -123,14 +143,9 @@ add_keys (void *cls,
{
struct DenomSig *d = &akc->d_sigs[i];
enum GNUNET_DB_QueryStatus qs;
- bool is_active = false;
struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
- struct TALER_DenominationPublicKey denom_pub;
/* For idempotency, check if the key is already active */
- memset (&denom_pub,
- 0,
- sizeof (denom_pub));
qs = TEH_plugin->lookup_denomination_key (
TEH_plugin->cls,
&d->h_denom_pub,
@@ -146,78 +161,21 @@ add_keys (void *cls,
"lookup denomination key");
return qs;
}
- if (0 == qs)
- {
- enum GNUNET_GenericReturnValue rv;
-
- rv = TEH_keys_load_fees (&d->h_denom_pub,
- &denom_pub,
- &meta);
- switch (rv)
- {
- case GNUNET_SYSERR:
- *mhd_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));
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_NO:
- *mhd_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));
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_OK:
- break;
- }
- }
- else
- {
- is_active = true;
- }
-
- /* check signature is valid */
- if (GNUNET_OK !=
- TALER_exchange_offline_denom_validity_verify (
- &d->h_denom_pub,
- meta.start,
- meta.expire_withdraw,
- meta.expire_deposit,
- meta.expire_legal,
- &meta.value,
- &meta.fee_withdraw,
- &meta.fee_deposit,
- &meta.fee_refresh,
- &meta.fee_refund,
- &TEH_master_public_key,
- &d->master_sig))
- {
- GNUNET_break_op (0);
- *mhd_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));
- if (! is_active)
- TALER_denom_pub_free (&denom_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (is_active)
+ 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,
- &denom_pub,
- &meta,
+ &d->denom_pub,
+ &d->meta,
&d->master_sig);
- TALER_denom_pub_free (&denom_pub);
if (qs < 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@@ -239,7 +197,6 @@ add_keys (void *cls,
{
struct SigningSig *s = &akc->s_sigs[i];
enum GNUNET_DB_QueryStatus qs;
- bool is_active = false;
struct TALER_EXCHANGEDB_SignkeyMetaData meta;
qs = TEH_plugin->lookup_signing_key (
@@ -257,46 +214,9 @@ add_keys (void *cls,
"lookup signing key");
return qs;
}
- if (0 == qs)
- {
- if (GNUNET_OK !=
- TEH_keys_get_timing (&s->exchange_pub,
- &meta))
- {
- /* For idempotency, check if the key is already active */
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_UNKNOWN,
- TALER_B2S (&s->exchange_pub));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- else
- {
- is_active = true; /* if we pass, it's active! */
- }
-
- /* check signature is valid */
- if (GNUNET_OK !=
- TALER_exchange_offline_signkey_validity_verify (
- &s->exchange_pub,
- meta.start,
- meta.expire_sign,
- meta.expire_legal,
- &TEH_master_public_key,
- &s->master_sig))
- {
- GNUNET_break_op (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_ADD_SIGNATURE_INVALID,
- TALER_B2S (&s->exchange_pub));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (is_active)
+ 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));
@@ -305,7 +225,7 @@ add_keys (void *cls,
qs = TEH_plugin->activate_signing_key (
TEH_plugin->cls,
&s->exchange_pub,
- &meta,
+ &s->meta,
&s->master_sig);
if (qs < 0)
{
@@ -327,22 +247,40 @@ add_keys (void *cls,
}
+/**
+ * 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;
- json_t *denom_sigs;
- json_t *signkey_sigs;
+ struct AddKeysContext akc = { 0 };
+ const json_t *denom_sigs;
+ const json_t *signkey_sigs;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("denom_sigs",
- &denom_sigs),
- GNUNET_JSON_spec_json ("signkey_sigs",
- &signkey_sigs),
+ GNUNET_JSON_spec_array_const ("denom_sigs",
+ &denom_sigs),
+ GNUNET_JSON_spec_array_const ("signkey_sigs",
+ &signkey_sigs),
GNUNET_JSON_spec_end ()
};
- bool ok;
MHD_RESULT ret;
{
@@ -356,23 +294,23 @@ TEH_handler_management_post_keys (
if (GNUNET_NO == res)
return MHD_YES; /* failure */
}
- if (! (json_is_array (denom_sigs) &&
- json_is_array (signkey_sigs)) )
+ 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);
- GNUNET_JSON_parse_free (spec);
return TALER_MHD_reply_with_error (
connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "array expected for denom_sigs and signkey_sigs");
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ "no key state (not even for management)");
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received /management/keys\n");
+
akc.nd_sigs = json_array_size (denom_sigs);
akc.d_sigs = GNUNET_new_array (akc.nd_sigs,
struct DenomSig);
- ok = true;
for (unsigned int i = 0; i<akc.nd_sigs; i++)
{
struct DenomSig *d = &akc.d_sigs[i];
@@ -389,27 +327,64 @@ TEH_handler_management_post_keys (
json_array_get (denom_sigs,
i),
ispec);
- if (GNUNET_SYSERR == res)
+ if (GNUNET_OK != res)
{
- ret = MHD_NO; /* hard failure */
- ok = false;
- break;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failure to handle /management/keys\n");
+ cleanup_akc (&akc);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
- if (GNUNET_NO == res)
+
+ res = TEH_keys_load_fees (akc.ksh,
+ &d->h_denom_pub,
+ &d->denom_pub,
+ &d->meta);
+ switch (res)
{
- ret = MHD_YES;
- ok = false;
+ 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;
+ }
}
- if (! ok)
- {
- GNUNET_free (akc.d_sigs);
- GNUNET_JSON_parse_free (spec);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failure to handle /management/keys\n");
- return ret;
- }
+
akc.ns_sigs = json_array_size (signkey_sigs);
akc.s_sigs = GNUNET_new_array (akc.ns_sigs,
struct SigningSig);
@@ -429,27 +404,58 @@ TEH_handler_management_post_keys (
json_array_get (signkey_sigs,
i),
ispec);
- if (GNUNET_SYSERR == res)
+ if (GNUNET_OK != res)
{
- ret = MHD_NO; /* hard failure */
- ok = false;
- break;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failure to handle /management/keys\n");
+ cleanup_akc (&akc);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
- if (GNUNET_NO == res)
+ res = TEH_keys_get_timing (&s->exchange_pub,
+ &s->meta);
+ switch (res)
{
- ret = MHD_YES;
- ok = false;
+ 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;
}
- }
- if (! ok)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failure to handle /management/keys\n");
- GNUNET_free (akc.d_sigs);
- GNUNET_free (akc.s_sigs);
- GNUNET_JSON_parse_free (spec);
- return ret;
+
+ /* 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",
@@ -460,13 +466,11 @@ TEH_handler_management_post_keys (
res = TEH_DB_run_transaction (connection,
"add keys",
- TEH_MT_OTHER,
+ TEH_MT_REQUEST_OTHER,
&ret,
&add_keys,
&akc);
- GNUNET_free (akc.d_sigs);
- GNUNET_free (akc.s_sigs);
- GNUNET_JSON_parse_free (spec);
+ cleanup_akc (&akc);
if (GNUNET_SYSERR == res)
return ret;
}
diff --git a/src/exchange/taler-exchange-httpd_management_signkey_EP_revoke.c b/src/exchange/taler-exchange-httpd_management_signkey_EP_revoke.c
index 0d6317b47..6c3683fc5 100644
--- a/src/exchange/taler-exchange-httpd_management_signkey_EP_revoke.c
+++ b/src/exchange/taler-exchange-httpd_management_signkey_EP_revoke.c
@@ -56,6 +56,7 @@ TEH_handler_management_signkeys_EP_revoke (
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,
diff --git a/src/exchange/taler-exchange-httpd_management_wire_disable.c b/src/exchange/taler-exchange-httpd_management_wire_disable.c
index d776bc353..e0b8a3de8 100644
--- a/src/exchange/taler-exchange-httpd_management_wire_disable.c
+++ b/src/exchange/taler-exchange-httpd_management_wire_disable.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -28,7 +28,7 @@
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_wire.h"
+#include "taler-exchange-httpd_keys.h"
/**
@@ -103,7 +103,7 @@ del_wire (void *cls,
NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- if (0 == qs)
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
*mhd_ret = TALER_MHD_reply_with_error (
connection,
@@ -114,7 +114,13 @@ del_wire (void *cls,
}
qs = TEH_plugin->update_wire (TEH_plugin->cls,
awc->payto_uri,
+ NULL,
+ NULL,
+ NULL,
awc->validity_end,
+ NULL,
+ NULL,
+ 0,
false);
if (qs < 0)
{
@@ -140,8 +146,8 @@ TEH_handler_management_post_wire_disable (
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("master_sig_del",
&awc.master_sig),
- GNUNET_JSON_spec_string ("payto_uri",
- &awc.payto_uri),
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &awc.payto_uri),
GNUNET_JSON_spec_timestamp ("validity_end",
&awc.validity_end),
GNUNET_JSON_spec_end ()
@@ -179,7 +185,7 @@ TEH_handler_management_post_wire_disable (
res = TEH_DB_run_transaction (connection,
"del wire",
- TEH_MT_OTHER,
+ TEH_MT_REQUEST_OTHER,
&ret,
&del_wire,
&awc);
diff --git a/src/exchange/taler-exchange-httpd_management_wire_enable.c b/src/exchange/taler-exchange-httpd_management_wire_enable.c
index 56828eb5e..472e19d3e 100644
--- a/src/exchange/taler-exchange-httpd_management_wire_enable.c
+++ b/src/exchange/taler-exchange-httpd_management_wire_enable.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ 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
@@ -29,7 +29,7 @@
#include "taler_signatures.h"
#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_wire.h"
+#include "taler-exchange-httpd_keys.h"
/**
@@ -55,10 +55,35 @@ struct AddWireContext
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;
+
};
@@ -111,15 +136,26 @@ add_wire (void *cls,
NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- if (0 == qs)
+ 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->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)
{
@@ -141,16 +177,34 @@ TEH_handler_management_post_wire (
struct MHD_Connection *connection,
const json_t *root)
{
- struct AddWireContext awc;
+ 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),
- GNUNET_JSON_spec_string ("payto_uri",
- &awc.payto_uri),
+ 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 ()
};
@@ -165,25 +219,55 @@ TEH_handler_management_post_wire (
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.validity_start,
- &TEH_master_public_key,
- &awc.master_sig_add))
+ 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,
- &TEH_master_public_key,
- &awc.master_sig_wire))
+ 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,
@@ -199,6 +283,7 @@ TEH_handler_management_post_wire (
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,
@@ -214,10 +299,11 @@ TEH_handler_management_post_wire (
res = TEH_DB_run_transaction (connection,
"add wire",
- TEH_MT_OTHER,
+ TEH_MT_REQUEST_OTHER,
&ret,
&add_wire,
&awc);
+ GNUNET_JSON_parse_free (spec);
if (GNUNET_SYSERR == res)
return ret;
}
diff --git a/src/exchange/taler-exchange-httpd_management_wire_fees.c b/src/exchange/taler-exchange-httpd_management_wire_fees.c
index c14500e8d..cb87592a5 100644
--- a/src/exchange/taler-exchange-httpd_management_wire_fees.c
+++ b/src/exchange/taler-exchange-httpd_management_wire_fees.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020, 2021 Taler Systems SA
+ Copyright (C) 2020, 2021, 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -29,7 +29,7 @@
#include "taler_signatures.h"
#include "taler-exchange-httpd_management.h"
#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_wire.h"
+#include "taler-exchange-httpd_keys.h"
/**
@@ -58,14 +58,9 @@ struct AddFeeContext
struct GNUNET_TIME_Timestamp end_time;
/**
- * Wire fee amount.
+ * Wire fee amounts.
*/
- struct TALER_Amount wire_fee;
-
- /**
- * Closing fee amount.
- */
- struct TALER_Amount closing_fee;
+ struct TALER_WireFeeSet fees;
};
@@ -91,16 +86,14 @@ add_fee (void *cls,
{
struct AddFeeContext *afc = cls;
enum GNUNET_DB_QueryStatus qs;
- struct TALER_Amount wire_fee;
- struct TALER_Amount closing_fee;
+ struct TALER_WireFeeSet fees;
qs = TEH_plugin->lookup_wire_fee_by_time (
TEH_plugin->cls,
afc->wire_method,
afc->start_time,
afc->end_time,
- &wire_fee,
- &closing_fee);
+ &fees);
if (qs < 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@@ -115,13 +108,10 @@ add_fee (void *cls,
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
{
if ( (GNUNET_OK ==
- TALER_amount_is_valid (&wire_fee)) &&
- (0 ==
- TALER_amount_cmp (&wire_fee,
- &afc->wire_fee)) &&
+ TALER_amount_is_valid (&fees.wire)) &&
(0 ==
- TALER_amount_cmp (&closing_fee,
- &afc->closing_fee)) )
+ TALER_wire_fee_set_cmp (&fees,
+ &afc->fees)) )
{
/* this will trigger the 'success' response */
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
@@ -142,8 +132,7 @@ add_fee (void *cls,
afc->wire_method,
afc->start_time,
afc->end_time,
- &afc->wire_fee,
- &afc->closing_fee,
+ &afc->fees,
&afc->master_sig);
if (qs < 0)
{
@@ -175,12 +164,12 @@ TEH_handler_management_post_wire_fees (
&afc.start_time),
GNUNET_JSON_spec_timestamp ("fee_end",
&afc.end_time),
- TALER_JSON_spec_amount ("closing_fee",
- TEH_currency,
- &afc.closing_fee),
TALER_JSON_spec_amount ("wire_fee",
TEH_currency,
- &afc.wire_fee),
+ &afc.fees.wire),
+ TALER_JSON_spec_amount ("closing_fee",
+ TEH_currency,
+ &afc.fees.closing),
GNUNET_JSON_spec_end ()
};
@@ -196,13 +185,13 @@ TEH_handler_management_post_wire_fees (
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.wire_fee,
- &afc.closing_fee,
+ &afc.fees,
&TEH_master_public_key,
&afc.master_sig))
{
@@ -221,7 +210,7 @@ TEH_handler_management_post_wire_fees (
res = TEH_DB_run_transaction (connection,
"add wire fee",
- TEH_MT_OTHER,
+ TEH_MT_REQUEST_OTHER,
&ret,
&add_fee,
&afc);
diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c
index 54f1385d7..ac3902e3f 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-2021 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
@@ -48,19 +48,17 @@ reply_melt_success (struct MHD_Connection *connection,
{
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)
- };
enum TALER_ErrorCode ec;
if (TALER_EC_NONE !=
- (ec = TEH_keys_exchange_sign (&body,
- &pub,
- &sig)))
+ (ec = TALER_exchange_online_melt_confirmation_sign (
+ &TEH_keys_exchange_sign_,
+ rc,
+ noreveal_index,
+ &pub,
+ &sig)))
{
+ GNUNET_break (0);
return TALER_MHD_reply_with_ec (connection,
ec,
NULL);
@@ -105,6 +103,11 @@ struct MeltContext
struct TALER_Amount coin_refresh_fee;
/**
+ * 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.
@@ -117,6 +120,10 @@ struct MeltContext
*/
bool coin_is_dirty;
+ /**
+ * True if @e rms is missing.
+ */
+ bool no_rms;
};
@@ -155,6 +162,9 @@ melt_transaction (void *cls,
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,
@@ -174,7 +184,6 @@ melt_transaction (void *cls,
if (rmc->zombie_required)
{
GNUNET_break_op (0);
- TEH_plugin->rollback (TEH_plugin->cls);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE,
@@ -183,15 +192,17 @@ melt_transaction (void *cls,
}
if (! balance_ok)
{
- TEH_plugin->rollback (TEH_plugin->cls);
+ 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;
}
@@ -224,12 +235,23 @@ database_melt (struct MHD_Connection *connection,
MHD_RESULT mhd_ret = MHD_NO;
enum GNUNET_DB_QueryStatus qs;
- qs = TEH_make_coin_known (&rmc->refresh_session.coin,
- connection,
- &rmc->known_coin_id,
- &mhd_ret);
- /* no transaction => no serialization failures should be possible */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ for (unsigned int tries = 0; tries<MAX_TRANSACTION_COMMIT_RETRIES; tries++)
+ {
+ 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_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "make_coin_known");
+ }
if (qs < 0)
return mhd_ret;
}
@@ -241,7 +263,7 @@ database_melt (struct MHD_Connection *connection,
if (GNUNET_OK !=
TEH_DB_run_transaction (connection,
"run melt",
- TEH_MT_MELT,
+ TEH_MT_REQUEST_MELT,
&mhd_ret,
&melt_transaction,
rmc))
@@ -267,7 +289,7 @@ static MHD_RESULT
check_melt_valid (struct MHD_Connection *connection,
struct MeltContext *rmc)
{
- /* Baseline: check if deposits/refreshs are generally
+ /* Baseline: check if deposits/refreshes are generally
simply still allowed for this denomination */
struct TEH_DenominationKey *dk;
MHD_RESULT mret;
@@ -278,6 +300,7 @@ check_melt_valid (struct MHD_Connection *connection,
&mret);
if (NULL == dk)
return mret;
+
if (GNUNET_TIME_absolute_is_past (dk->meta.expire_legal.abs_time))
{
/* Way too late now, even zombies have expired */
@@ -287,6 +310,7 @@ check_melt_valid (struct MHD_Connection *connection,
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
"MELT");
}
+
if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
{
/* This denomination is not yet valid */
@@ -297,8 +321,13 @@ check_melt_valid (struct MHD_Connection *connection,
"MELT");
}
- rmc->coin_refresh_fee = dk->meta.fee_refresh;
+ rmc->coin_refresh_fee = dk->meta.fees.refresh;
rmc->coin_value = dk->meta.value;
+
+ 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 <
TALER_amount_cmp (&rmc->coin_refresh_fee,
@@ -310,7 +339,17 @@ check_melt_valid (struct MHD_Connection *connection,
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))
@@ -323,11 +362,13 @@ check_melt_valid (struct MHD_Connection *connection,
}
/* 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))
{
@@ -341,7 +382,7 @@ check_melt_valid (struct MHD_Connection *connection,
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_DenominationHash denom_hash;
+ struct TALER_DenominationHashP denom_hash;
enum GNUNET_DB_QueryStatus qs;
/* Check that the coin is dirty (we have seen it before), as we will
@@ -403,6 +444,10 @@ TEH_handler_melt (struct MHD_Connection *connection,
&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",
@@ -410,12 +455,14 @@ TEH_handler_melt (struct MHD_Connection *connection,
&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;
{
diff --git a/src/exchange/taler-exchange-httpd_melt.h b/src/exchange/taler-exchange-httpd_melt.h
index 0edaf2475..b15fd07c7 100644
--- a/src/exchange/taler-exchange-httpd_melt.h
+++ b/src/exchange/taler-exchange-httpd_melt.h
@@ -30,10 +30,10 @@
/**
* 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
diff --git a/src/exchange/taler-exchange-httpd_metrics.c b/src/exchange/taler-exchange-httpd_metrics.c
index 8c8cd343a..1542801fe 100644
--- a/src/exchange/taler-exchange-httpd_metrics.c
+++ b/src/exchange/taler-exchange-httpd_metrics.c
@@ -29,9 +29,19 @@
#include <jansson.h>
-unsigned long long TEH_METRICS_num_requests[TEH_MT_COUNT];
+unsigned long long TEH_METRICS_num_requests[TEH_MT_REQUEST_COUNT];
-unsigned long long TEH_METRICS_num_conflict[TEH_MT_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
@@ -44,6 +54,11 @@ TEH_handler_metrics (struct TEH_RequestContext *rc,
(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"
@@ -57,23 +72,85 @@ TEH_handler_metrics (struct TEH_RequestContext *rc,
"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_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_OTHER],
+ TEH_METRICS_num_conflict[TEH_MT_REQUEST_OTHER],
"deposit",
- TEH_METRICS_num_conflict[TEH_MT_DEPOSIT],
+ TEH_METRICS_num_conflict[TEH_MT_REQUEST_DEPOSIT],
"withdraw",
- TEH_METRICS_num_conflict[TEH_MT_WITHDRAW],
+ TEH_METRICS_num_conflict[TEH_MT_REQUEST_WITHDRAW],
"melt",
- TEH_METRICS_num_conflict[TEH_MT_MELT],
+ TEH_METRICS_num_conflict[TEH_MT_REQUEST_MELT],
"other",
- TEH_METRICS_num_requests[TEH_MT_OTHER],
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_OTHER],
"deposit",
- TEH_METRICS_num_requests[TEH_MT_DEPOSIT],
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_DEPOSIT],
"withdraw",
- TEH_METRICS_num_requests[TEH_MT_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_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);
diff --git a/src/exchange/taler-exchange-httpd_metrics.h b/src/exchange/taler-exchange-httpd_metrics.h
index 55e5372a7..318113c1f 100644
--- a/src/exchange/taler-exchange-httpd_metrics.h
+++ b/src/exchange/taler-exchange-httpd_metrics.h
@@ -29,27 +29,97 @@
/**
* Request types for which we collect metrics.
*/
-enum TEH_MetricType
+enum TEH_MetricTypeRequest
{
- TEH_MT_OTHER = 0,
- TEH_MT_DEPOSIT = 1,
- TEH_MT_WITHDRAW = 2,
- TEH_MT_MELT = 3,
- TEH_MT_COUNT = 4 /* MUST BE LAST! */
+ 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_COUNT];
+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_COUNT];
+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.
diff --git a/src/exchange/taler-exchange-httpd_mhd.h b/src/exchange/taler-exchange-httpd_mhd.h
index 270b0539a..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
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/auditor/taler-auditor-httpd_exchanges.h b/src/exchange/taler-exchange-httpd_purses_create.h
index c7d8dd5fd..7ccba1446 100644
--- a/src/auditor/taler-auditor-httpd_exchanges.h
+++ b/src/exchange/taler-exchange-httpd_purses_create.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2018 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,33 +14,34 @@
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_purses_create.h
+ * @brief Handle /purses/$PID/create requests
* @author Christian Grothoff
*/
-#ifndef TALER_AUDITOR_HTTPD_EXCHANGES_H
-#define TALER_AUDITOR_HTTPD_EXCHANGES_H
+#ifndef TALER_EXCHANGE_HTTPD_PURSES_CREATE_H
+#define TALER_EXCHANGE_HTTPD_PURSES_CREATE_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 "/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 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 purse_pub public key of the purse
+ * @param root uploaded JSON data
* @return MHD result code
- */
+ */
MHD_RESULT
-TAH_EXCHANGES_handler (struct TAH_RequestHandler *rh,
- struct MHD_Connection *connection,
- void **connection_cls,
- const char *upload_data,
- size_t *upload_data_size);
+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
index 78a454c85..a5d5b2ab4 100644
--- a/src/exchange/taler-exchange-httpd_recoup-refresh.c
+++ b/src/exchange/taler-exchange-httpd_recoup-refresh.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2017-2021 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 Affero General Public License as published by the Free Software
@@ -55,7 +55,7 @@ struct RecoupContext
/**
* Key used to blind the coin.
*/
- const union TALER_DenominationBlindingKeyP *coin_bks;
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks;
/**
* Signature of the coin requesting recoup.
@@ -146,6 +146,7 @@ recoup_refresh_transaction (void *cls,
*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;
}
@@ -162,7 +163,10 @@ recoup_refresh_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 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
*/
@@ -170,13 +174,15 @@ static MHD_RESULT
verify_and_execute_recoup_refresh (
struct MHD_Connection *connection,
const struct TALER_CoinPublicInfo *coin,
- const union TALER_DenominationBlindingKeyP *coin_bks,
+ 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_BlindedCoinHash h_blind;
+ struct TALER_BlindedCoinHashP h_blind;
/* check denomination exists and is in recoup mode */
dk = TEH_keys_denomination_by_hash (&coin->denom_pub_hash,
@@ -213,6 +219,17 @@ verify_and_execute_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))
@@ -226,6 +243,7 @@ verify_and_execute_recoup_refresh (
}
/* 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,
@@ -241,18 +259,18 @@ verify_and_execute_recoup_refresh (
}
{
- void *coin_ev;
- size_t coin_ev_size;
- struct TALER_CoinPubHash c_hash;
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_BlindedPlanchet blinded_planchet;
if (GNUNET_OK !=
TALER_denom_blind (&dk->denom_pub,
coin_bks,
- NULL, /* FIXME-Oec: TALER_AgeHash * */
+ nonce,
+ &coin->h_age_commitment,
&coin->coin_pub,
+ exchange_vals,
&c_hash,
- &coin_ev,
- &coin_ev_size))
+ &blinded_planchet))
{
GNUNET_break (0);
return TALER_MHD_reply_with_error (
@@ -261,10 +279,10 @@ verify_and_execute_recoup_refresh (
TALER_EC_EXCHANGE_RECOUP_REFRESH_BLINDING_FAILED,
NULL);
}
- TALER_coin_ev_hash (coin_ev,
- coin_ev_size,
+ TALER_coin_ev_hash (&blinded_planchet,
+ &coin->denom_pub_hash,
&h_blind);
- GNUNET_free (coin_ev);
+ TALER_blinded_planchet_free (&blinded_planchet);
}
pc.coin_sig = coin_sig;
@@ -322,7 +340,7 @@ verify_and_execute_recoup_refresh (
if (GNUNET_OK !=
TEH_DB_run_transaction (connection,
"run recoup-refresh",
- TEH_MT_OTHER,
+ TEH_MT_REQUEST_OTHER,
&mhd_ret,
&recoup_refresh_transaction,
&pc))
@@ -354,18 +372,32 @@ TEH_handler_recoup_refresh (struct MHD_Connection *connection,
const json_t *root)
{
enum GNUNET_GenericReturnValue ret;
- struct TALER_CoinPublicInfo coin;
- union TALER_DenominationBlindingKeyP coin_bks;
+ 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 ()
};
@@ -385,7 +417,11 @@ TEH_handler_recoup_refresh (struct MHD_Connection *connection,
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;
diff --git a/src/exchange/taler-exchange-httpd_recoup-refresh.h b/src/exchange/taler-exchange-httpd_recoup-refresh.h
index 25c12fac3..94ae7f889 100644
--- a/src/exchange/taler-exchange-httpd_recoup-refresh.h
+++ b/src/exchange/taler-exchange-httpd_recoup-refresh.h
@@ -14,7 +14,7 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file taler-exchange-httpd_recoup_refresh.h
+ * @file taler-exchange-httpd_recoup-refresh.h
* @brief Handle /recoup-refresh requests
* @author Christian Grothoff
*/
diff --git a/src/exchange/taler-exchange-httpd_recoup.c b/src/exchange/taler-exchange-httpd_recoup.c
index 0deaa8bbb..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-2021 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
@@ -40,9 +40,9 @@
struct RecoupContext
{
/**
- * Hash of the blinded coin.
+ * Hash identifying the withdraw request.
*/
- struct TALER_BlindedCoinHash h_blind;
+ struct TALER_BlindedCoinHashP h_coin_ev;
/**
* Set by #recoup_transaction() to the reserve that will
@@ -58,7 +58,7 @@ struct RecoupContext
/**
* Key used to blind the coin.
*/
- const union TALER_DenominationBlindingKeyP *coin_bks;
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks;
/**
* Signature of the coin requesting recoup.
@@ -149,6 +149,7 @@ recoup_transaction (void *cls,
*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;
}
@@ -165,7 +166,10 @@ 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
* @return MHD result code
*/
@@ -173,7 +177,9 @@ static MHD_RESULT
verify_and_execute_recoup (
struct MHD_Connection *connection,
const struct TALER_CoinPublicInfo *coin,
- const union TALER_DenominationBlindingKeyP *coin_bks,
+ 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;
@@ -215,6 +221,17 @@ verify_and_execute_recoup (
}
/* 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))
@@ -228,6 +245,7 @@ verify_and_execute_recoup (
}
/* 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,
@@ -242,19 +260,22 @@ verify_and_execute_recoup (
NULL);
}
+ /* re-compute client-side blinding so we can
+ (a bit later) check that this coin was indeed
+ signed by us. */
{
- void *coin_ev;
- size_t coin_ev_size;
- struct TALER_CoinPubHash c_hash;
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_BlindedPlanchet blinded_planchet;
if (GNUNET_OK !=
TALER_denom_blind (&dk->denom_pub,
coin_bks,
- NULL, /* FIXME-Oec: TALER_AgeHash * */
+ nonce,
+ &coin->h_age_commitment,
&coin->coin_pub,
+ exchange_vals,
&c_hash,
- &coin_ev,
- &coin_ev_size))
+ &blinded_planchet))
{
GNUNET_break (0);
return TALER_MHD_reply_with_error (
@@ -263,10 +284,10 @@ verify_and_execute_recoup (
TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED,
NULL);
}
- TALER_coin_ev_hash (coin_ev,
- coin_ev_size,
- &pc.h_blind);
- GNUNET_free (coin_ev);
+ TALER_coin_ev_hash (&blinded_planchet,
+ &coin->denom_pub_hash,
+ &pc.h_coin_ev);
+ TALER_blinded_planchet_free (&blinded_planchet);
}
pc.coin_sig = coin_sig;
@@ -292,7 +313,7 @@ verify_and_execute_recoup (
enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->get_reserve_by_h_blind (TEH_plugin->cls,
- &pc.h_blind,
+ &pc.h_coin_ev,
&pc.reserve_pub,
&pc.reserve_out_serial_id);
if (0 > qs)
@@ -308,7 +329,7 @@ verify_and_execute_recoup (
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Recoup requested for unknown envelope %s\n",
- GNUNET_h2s (&pc.h_blind.hash));
+ GNUNET_h2s (&pc.h_coin_ev.hash));
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_NOT_FOUND,
@@ -324,7 +345,7 @@ verify_and_execute_recoup (
if (GNUNET_OK !=
TEH_DB_run_transaction (connection,
"run recoup",
- TEH_MT_OTHER,
+ TEH_MT_REQUEST_OTHER,
&mhd_ret,
&recoup_transaction,
&pc))
@@ -357,17 +378,31 @@ TEH_handler_recoup (struct MHD_Connection *connection,
{
enum GNUNET_GenericReturnValue ret;
struct TALER_CoinPublicInfo coin;
- union TALER_DenominationBlindingKeyP coin_bks;
+ 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: 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 ()
};
@@ -387,7 +422,11 @@ TEH_handler_recoup (struct MHD_Connection *connection,
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_refreshes_reveal.c b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
index 30a7294c1..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, 2021 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
@@ -32,17 +32,11 @@
/**
- * Maximum number of fresh coins we allow per refresh operation.
- */
-#define MAX_FRESH_COINS 256
-
-
-/**
* Send a response for "/refreshes/$RCH/reveal".
*
* @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 MHD_RESULT
@@ -109,15 +103,35 @@ struct RevealContext
const struct TEH_DenominationKey **dks;
/**
+ * Age commitment that was used for the original coin. If not NULL, its hash
+ * should be the same as melt.session.h_age_commitment.
+ */
+ struct TALER_AgeCommitment *old_age_commitment;
+
+ /**
+ * Array of information about fresh coins being revealed.
+ */
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs;
+
+ /**
* Envelopes to be signed.
*/
struct TALER_RefreshCoinData *rcds;
/**
+ * Refresh master secret.
+ */
+ struct TALER_RefreshMasterSecretP rms;
+
+ /**
* Size of the @e dks, @e rcds and @e ev_sigs arrays (if non-NULL).
*/
unsigned int num_fresh_coins;
+ /**
+ * True if @e rms was not provided.
+ */
+ bool no_rms;
};
@@ -142,6 +156,89 @@ check_commitment (struct RevealContext *rctx,
struct MHD_Connection *connection,
MHD_RESULT *mhd_ret)
{
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonces[rctx->num_fresh_coins];
+
+ memset (nonces,
+ 0,
+ sizeof (nonces));
+ for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
+ {
+ 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;
+ }
+ }
+
+ // OPTIMIZE: do this in batch later!
+ for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
+ {
+ 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]
@@ -169,9 +266,12 @@ check_commitment (struct RevealContext *rctx,
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,
&rctx->melt.session.coin.coin_pub,
&ts);
@@ -180,26 +280,64 @@ check_commitment (struct RevealContext *rctx,
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_CoinPubHash c_hash;
+ 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_planchet_setup_refresh (&ts,
- j,
- &ps);
+ 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;
+ }
+
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,
&rctx->melt.session.coin.coin_pub,
@@ -216,7 +354,7 @@ check_commitment (struct RevealContext *rctx,
{
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);
}
@@ -248,7 +386,7 @@ check_commitment (struct RevealContext *rctx,
if ( (0 >
TALER_amount_add (&total,
- &rctx->dks[i]->meta.fee_withdraw,
+ &rctx->dks[i]->meta.fees.withdraw,
&rctx->dks[i]->meta.value)) ||
(0 >
TALER_amount_add (&refresh_cost,
@@ -285,24 +423,29 @@ check_commitment (struct RevealContext *rctx,
* @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 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 *coin_evs)
+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 */
+ /* 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_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));
@@ -317,6 +460,61 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
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++)
{
@@ -334,13 +532,22 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
-1);
if (GNUNET_OK != res)
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
- dks[i] = TEH_keys_denomination_by_hash2 (ksh,
- &rrcs[i].h_denom_pub,
- connection,
- &ret);
+ 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");
+ }
if (GNUNET_TIME_absolute_is_past (dks[i]->meta.expire_withdraw.abs_time))
{
/* This denomination is past the expiration time for withdraws */
@@ -375,9 +582,8 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
{
struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_varsize (NULL,
- &rrc->coin_ev,
- &rrc->coin_ev_size),
+ TALER_JSON_spec_blinded_planchet (NULL,
+ &rrc->blinded_planchet),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue res;
@@ -390,59 +596,85 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
if (GNUNET_OK != res)
{
for (unsigned int j = 0; j<i; j++)
- GNUNET_free (rrcs[j].coin_ev);
+ TALER_blinded_planchet_free (&rrcs[j].blinded_planchet);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
- GNUNET_CRYPTO_hash (rrc->coin_ev,
- rrc->coin_ev_size,
- &rrc->coin_envelope_hash.hash);
+ 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,
- &rctx->rc,
- &rctx->melt,
- &melt_serial_id)))
+ /* 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:
- 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;
+ 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;
}
- if (rctx->melt.session.noreveal_index >= TALER_CNC_KAPPA)
+
+ /* Sanity check: Compare hash from melting with hash of this age commitment */
{
- GNUNET_break (0);
- ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "melt");
- goto cleanup;
+ 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++)
{
@@ -460,7 +692,9 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
-1);
if (GNUNET_OK != res)
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+
/* Check signature */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
if (GNUNET_OK !=
TALER_wallet_link_verify (
&rrcs[i].h_denom_pub,
@@ -485,12 +719,24 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
struct TALER_RefreshCoinData *rcd = &rcds[i];
- rcd->coin_ev = rrc->coin_ev;
- rcd->coin_ev_size = rrc->coin_ev_size;
+ 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->dks = dks;
rctx->rcds = rcds;
+ rctx->rrcs = rrcs;
if (GNUNET_OK !=
check_commitment (rctx,
connection,
@@ -500,17 +746,23 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Creating %u signatures\n",
(unsigned int) rctx->num_fresh_coins);
+
/* create fresh coin signatures */
- for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
{
- enum TALER_ErrorCode ec = TALER_EC_NONE;
+ struct TEH_CoinSignData csds[rctx->num_fresh_coins];
+ struct TALER_BlindedDenominationSignature bss[rctx->num_fresh_coins];
+ enum TALER_ErrorCode ec;
- rrcs[i].coin_sig
- = TEH_keys_denomination_sign (
- &rrcs[i].h_denom_pub,
- rcds[i].coin_ev,
- rcds[i].coin_ev_size,
- &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);
@@ -519,39 +771,88 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
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");
- /* Persist operation result in DB */
+
+
+ for (unsigned int r = 0; r<MAX_TRANSACTION_COMMIT_RETRIES; r++)
{
- enum GNUNET_DB_QueryStatus qs;
+ bool changed;
- for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
+ /* Persist operation result in DB */
+ if (GNUNET_OK !=
+ TEH_plugin->start (TEH_plugin->cls,
+ "insert_refresh_reveal batch"))
{
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ NULL);
+ goto cleanup;
+ }
- rrc->coin_ev = rcds[i].coin_ev;
- rrc->coin_ev_size = rcds[i].coin_ev_size;
+ 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)
+ {
+ TEH_plugin->rollback (TEH_plugin->cls);
+ continue;
}
- 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);
/* 0 == qs is ok, as we did not check for repeated requests */
- if (0 > qs)
+ if (GNUNET_DB_STATUS_HARD_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_STORE_FAILED,
"insert_refresh_reveal");
goto cleanup;
}
+ changed = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ qs = TEH_plugin->commit (TEH_plugin->cls);
+ if (qs >= 0)
+ {
+ if (changed)
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_REFRESH_REVEAL]++;
+ break; /* success */
+ }
+ if (GNUNET_DB_STATUS_HARD_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_COMMIT_FAILED,
+ NULL);
+ goto cleanup;
+ }
+ 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,
@@ -562,9 +863,12 @@ cleanup:
for (unsigned int i = 0; i<num_fresh_coins; i++)
{
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);
- GNUNET_free (rrc->coin_ev);
+ TALER_blinded_planchet_free (&rrc->blinded_planchet);
}
return ret;
}
@@ -577,11 +881,18 @@ 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
*/
@@ -591,19 +902,20 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection,
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_EXCHANGE_REFRESHES_REVEAL_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE,
+ TALER_EC_EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE,
NULL);
}
@@ -626,6 +938,19 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection,
"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++)
{
@@ -649,6 +974,7 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection,
rctx,
link_sigs_json,
new_denoms_h_json,
+ old_age_commitment_json,
coin_evs);
}
@@ -658,22 +984,31 @@ 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_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 ()
};
@@ -719,7 +1054,6 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,
/* 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 (rc->connection,
MHD_HTTP_BAD_REQUEST,
@@ -727,18 +1061,13 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,
NULL);
}
- {
- MHD_RESULT res;
-
- res = handle_refreshes_reveal_json (rc->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_refund.c b/src/exchange/taler-exchange-httpd_refund.c
index 9cc019a14..b8bcf7c60 100644
--- a/src/exchange/taler-exchange-httpd_refund.c
+++ b/src/exchange/taler-exchange-httpd_refund.c
@@ -50,22 +50,18 @@ reply_refund_success (struct MHD_Connection *connection,
{
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)
- };
enum TALER_ErrorCode ec;
- TALER_amount_hton (&rc.refund_amount,
- &refund->refund_amount);
if (TALER_EC_NONE !=
- (ec = TEH_keys_exchange_sign (&rc,
- &pub,
- &sig)))
+ (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_ec (connection,
ec,
@@ -162,10 +158,11 @@ refund_transaction (void *cls,
}
if (conflict)
{
- TEH_plugin->rollback (TEH_plugin->cls);
+ GNUNET_break_op (0);
*mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
connection,
TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT,
+ &refund->coin.denom_pub_hash,
&refund->coin.coin_pub);
return GNUNET_DB_STATUS_HARD_ERROR;
}
@@ -179,10 +176,10 @@ refund_transaction (void *cls,
}
if (! refund_ok)
{
- TEH_plugin->rollback (TEH_plugin->cls);
*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;
}
@@ -204,11 +201,11 @@ static MHD_RESULT
verify_and_execute_refund (struct MHD_Connection *connection,
struct TALER_EXCHANGEDB_Refund *refund)
{
- struct TALER_DenominationHash denom_hash;
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,
@@ -231,15 +228,16 @@ verify_and_execute_refund (struct MHD_Connection *connection,
qs = TEH_plugin->get_coin_denomination (TEH_plugin->cls,
&refund->coin.coin_pub,
&rctx.known_coin_id,
- &denom_hash);
+ &refund->coin.denom_pub_hash);
if (0 > qs)
{
MHD_RESULT res;
char *dhs;
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- dhs = GNUNET_STRINGS_data_to_string_alloc (&denom_hash,
- sizeof (denom_hash));
+ 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,
@@ -254,7 +252,7 @@ verify_and_execute_refund (struct MHD_Connection *connection,
struct TEH_DenominationKey *dk;
MHD_RESULT mret;
- dk = TEH_keys_denomination_by_hash (&denom_hash,
+ dk = TEH_keys_denomination_by_hash (&refund->coin.denom_pub_hash,
connection,
&mret);
if (NULL == dk)
@@ -264,8 +262,8 @@ verify_and_execute_refund (struct MHD_Connection *connection,
GNUNET_break (0);
return mret;
}
- refund->details.refund_fee = dk->meta.fee_refund;
- rctx.deposit_fee = dk->meta.fee_deposit;
+ refund->details.refund_fee = dk->meta.fees.refund;
+ rctx.deposit_fee = dk->meta.fees.deposit;
}
/* Finally run the actual transaction logic */
@@ -275,7 +273,7 @@ verify_and_execute_refund (struct MHD_Connection *connection,
if (GNUNET_OK !=
TEH_DB_run_transaction (connection,
"run refund",
- TEH_MT_OTHER,
+ TEH_MT_REQUEST_OTHER,
&mhd_ret,
&refund_transaction,
&rctx))
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 80c992e61..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-2021 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
@@ -52,8 +52,12 @@ struct ReservePoller
struct MHD_Connection *connection;
/**
- * Subscription for the database event we are
- * waiting for.
+ * Our request context.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Subscription for the database event we are waiting for.
*/
struct GNUNET_DB_EventHandler *eh;
@@ -63,6 +67,16 @@ struct ReservePoller
struct GNUNET_TIME_Absolute timeout;
/**
+ * Public key of the reserve the inquiry is about.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Balance of the reserve, set in the callback.
+ */
+ struct TALER_Amount balance;
+
+ /**
* True if we are still suspended.
*/
bool suspended;
@@ -84,13 +98,10 @@ static struct ReservePoller *rp_tail;
void
TEH_reserves_get_cleanup ()
{
- struct ReservePoller *rp;
-
- while (NULL != (rp = rp_head))
+ for (struct ReservePoller *rp = rp_head;
+ NULL != rp;
+ rp = rp->next)
{
- GNUNET_CONTAINER_DLL_remove (rp_head,
- rp_tail,
- rp);
if (rp->suspended)
{
rp->suspended = false;
@@ -115,11 +126,14 @@ rp_cleanup (struct TEH_RequestContext *rc)
if (NULL != rp->eh)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Cancelling DB event listening\n");
+ "Cancelling DB event listening on cleanup (odd unless during shutdown)\n");
TEH_plugin->event_listen_cancel (TEH_plugin->cls,
rp->eh);
rp->eh = NULL;
}
+ GNUNET_CONTAINER_DLL_remove (rp_head,
+ rp_tail,
+ rp);
GNUNET_free (rp);
}
@@ -137,26 +151,17 @@ db_event_cb (void *cls,
const void *extra,
size_t extra_size)
{
- struct TEH_RequestContext *rc = cls;
- struct ReservePoller *rp = rc->rh_ctx;
+ struct ReservePoller *rp = cls;
struct GNUNET_AsyncScopeSave old_scope;
(void) extra;
(void) extra_size;
- if (NULL == rp)
- return; /* event triggered while main transaction
- was still running */
if (! rp->suspended)
return; /* might get multiple wake-up events */
- rp->suspended = false;
- GNUNET_async_scope_enter (&rc->async_scope_id,
+ GNUNET_async_scope_enter (&rp->rc->async_scope_id,
&old_scope);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming from long-polling on reserve\n");
TEH_check_invariants ();
- GNUNET_CONTAINER_DLL_remove (rp_head,
- rp_tail,
- rp);
+ rp->suspended = false;
MHD_resume_connection (rp->connection);
TALER_MHD_daemon_trigger ();
TEH_check_invariants ();
@@ -164,205 +169,95 @@ db_event_cb (void *cls,
}
-/**
- * Send reserve history to client.
- *
- * @param connection connection to the client
- * @param rh reserve history to return
- * @return MHD result code
- */
-static MHD_RESULT
-reply_reserve_history_success (struct MHD_Connection *connection,
- 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_GENERIC_JSON_ALLOCATION_FAILURE,
- NULL);
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- TALER_JSON_pack_amount ("balance",
- &balance),
- GNUNET_JSON_pack_array_steal ("history",
- json_history));
-}
-
-
-/**
- * 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.
- */
- struct TALER_EXCHANGEDB_ReserveHistory *rh;
-
-};
-
-
-/**
- * Function implementing /reserves/ GET transaction.
- * Execute a /reserves/ GET. Given the public key of a reserve,
- * return the associated transaction history. Runs the
- * transaction logic; IF it returns a non-error code, the transaction
- * logic MUST NOT queue a MHD response. IF it returns an hard error,
- * the transaction logic MUST queue a MHD response and set @a mhd_ret.
- * IF it returns the soft error code, the function MAY be called again
- * to retry and MUST not queue a MHD response.
- *
- * @param cls a `struct ReserveHistoryContext *`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!); unused
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-reserve_history_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct ReserveHistoryContext *rsc = cls;
- struct TALER_Amount balance;
-
- (void) connection;
- (void) mhd_ret;
- return TEH_plugin->get_reserve_history (TEH_plugin->cls,
- &rsc->reserve_pub,
- &balance,
- &rsc->rh);
-}
-
-
MHD_RESULT
-TEH_handler_reserves_get (struct TEH_RequestContext *rc,
- const char *const args[1])
+TEH_handler_reserves_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub)
{
- struct ReserveHistoryContext rsc;
- MHD_RESULT mhd_ret;
- struct GNUNET_TIME_Relative timeout = GNUNET_TIME_UNIT_ZERO;
- struct GNUNET_DB_EventHandler *eh = NULL;
-
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &rsc.reserve_pub,
- sizeof (rsc.reserve_pub)))
+ struct ReservePoller *rp = rc->rh_ctx;
+
+ if (NULL == rp)
{
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_MERCHANT_GENERIC_RESERVE_PUB_MALFORMED,
- args[0]);
+ 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);
}
- {
- const char *long_poll_timeout_ms;
- long_poll_timeout_ms
- = MHD_lookup_connection_value (rc->connection,
- MHD_GET_ARGUMENT_KIND,
- "timeout_ms");
- if (NULL != long_poll_timeout_ms)
- {
- unsigned int timeout_ms;
- char dummy;
-
- if (1 != sscanf (long_poll_timeout_ms,
- "%u%c",
- &timeout_ms,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "timeout_ms (must be non-negative number)");
- }
- timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
- timeout_ms);
- }
- }
- if ( (! GNUNET_TIME_relative_is_zero (timeout)) &&
- (NULL == rc->rh_ctx) )
+ if ( (GNUNET_TIME_absolute_is_future (rp->timeout)) &&
+ (NULL == rp->eh) )
{
struct TALER_ReserveEventP rep = {
.header.size = htons (sizeof (rep)),
.header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
- .reserve_pub = rsc.reserve_pub
+ .reserve_pub = rp->reserve_pub
};
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Starting DB event listening\n");
- eh = TEH_plugin->event_listen (TEH_plugin->cls,
- timeout,
- &rep.header,
- &db_event_cb,
- rc);
- }
- rsc.rh = NULL;
- if (GNUNET_OK !=
- TEH_DB_run_transaction (rc->connection,
- "get reserve history",
- TEH_MT_OTHER,
- &mhd_ret,
- &reserve_history_transaction,
- &rsc))
- {
- if (NULL != eh)
- TEH_plugin->event_listen_cancel (TEH_plugin->cls,
- eh);
- return mhd_ret;
+ "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);
}
- /* generate proper response */
- if (NULL == rsc.rh)
{
- struct ReservePoller *rp = rc->rh_ctx;
+ enum GNUNET_DB_QueryStatus qs;
- if ( (NULL != rp) ||
- (GNUNET_TIME_relative_is_zero (timeout)) )
+ qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
+ &rp->reserve_pub,
+ &rp->balance);
+ switch (qs)
{
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0); /* single-shot query should never have soft-errors */
return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_RESERVES_GET_STATUS_UNKNOWN,
- args[0]);
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "get_reserve_balance");
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_reserve_balance");
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got reserve balance of %s\n",
+ TALER_amount2s (&rp->balance));
+ return TALER_MHD_REPLY_JSON_PACK (rc->connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_amount ("balance",
+ &rp->balance));
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ if (! GNUNET_TIME_absolute_is_future (rp->timeout))
+ {
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_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;
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Long-polling on reserve for %s\n",
- GNUNET_STRINGS_relative_time_to_string (timeout,
- GNUNET_YES));
- rp = GNUNET_new (struct ReservePoller);
- rp->connection = rc->connection;
- rp->timeout = GNUNET_TIME_relative_to_absolute (timeout);
- rp->eh = eh;
- rc->rh_ctx = rp;
- rc->rh_cleaner = &rp_cleanup;
- rp->suspended = true;
- GNUNET_CONTAINER_DLL_insert (rp_head,
- rp_tail,
- rp);
- MHD_suspend_connection (rc->connection);
- return MHD_YES;
}
- if (NULL != eh)
- TEH_plugin->event_listen_cancel (TEH_plugin->cls,
- eh);
- mhd_ret = reply_reserve_history_success (rc->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 30c6559f6..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
@@ -43,11 +43,12 @@ TEH_reserves_get_cleanup (void);
* status of the reserve.
*
* @param rc request context
- * @param args array of additional options (length: 1, just the reserve_pub)
+ * @param reserve_pub public key of the reserve
* @return MHD result code
*/
MHD_RESULT
-TEH_handler_reserves_get (struct TEH_RequestContext *rc,
- const char *const args[1]);
+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 66a3b0af9..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-2021 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,379 +23,21 @@
* @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_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)
-{
- 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_MerchantWireHash 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... */
- if (GNUNET_OK !=
- TALER_wallet_deposit_verify (&deposit->amount_with_fee,
- &deposit->deposit_fee,
- &h_wire,
- &deposit->h_contract_terms,
- NULL /* h_extensions! */,
- &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_data_auto ("h_denom_pub",
- &deposit->h_denom_pub),
- 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;
-
-#if ENABLE_SANITY_CHECKS
- if (GNUNET_OK !=
- TALER_wallet_melt_verify (&melt->amount_with_fee,
- &melt->melt_fee,
- &melt->rc,
- &melt->h_denom_pub,
- coin_pub,
- &melt->coin_sig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
-#endif
- 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_data_auto ("h_denom_pub",
- &melt->h_denom_pub),
- 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
- 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;
- struct TALER_RecoupRefreshConfirmationPS pc = {
- .purpose.purpose = htonl (
- TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH),
- .purpose.size = htonl (sizeof (pc)),
- .timestamp = GNUNET_TIME_timestamp_hton (pr->timestamp),
- .coin_pub = pr->coin.coin_pub,
- .old_coin_pub = pr->old_coin_pub
- };
-
- TALER_amount_hton (&pc.recoup_amount,
- &pr->value);
- if (TALER_EC_NONE !=
- TEH_keys_exchange_sign (&pc,
- &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;
- struct TALER_RecoupConfirmationPS pc = {
- .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP),
- .purpose.size = htonl (sizeof (pc)),
- .timestamp = GNUNET_TIME_timestamp_hton (recoup->timestamp),
- .coin_pub = *coin_pub,
- .reserve_pub = recoup->reserve_pub
- };
-
- TALER_amount_hton (&pc.recoup_amount,
- &recoup->value);
- if (TALER_EC_NONE !=
- TEH_keys_exchange_sign (&pc,
- &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 ("h_denom_pub",
- &recoup->h_denom_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;
- struct TALER_RecoupRefreshConfirmationPS pc = {
- .purpose.purpose = htonl (
- TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH),
- .purpose.size = htonl (sizeof (pc)),
- .timestamp = GNUNET_TIME_timestamp_hton (pr->timestamp),
- .coin_pub = *coin_pub,
- .old_coin_pub = pr->old_coin_pub
- };
-
- TALER_amount_hton (&pc.recoup_amount,
- &pr->value);
- if (TALER_EC_NONE !=
- TEH_keys_exchange_sign (&pc,
- &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 ("h_denom_pub",
- &pr->coin.denom_pub_hash),
- 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;
- }
- default:
- GNUNET_assert (0);
- }
- }
- return history;
-}
-
-
MHD_RESULT
TEH_RESPONSE_reply_unknown_denom_pub_hash (
struct MHD_Connection *connection,
- const struct TALER_DenominationHash *dph)
+ const struct TALER_DenominationHashP *dph)
{
struct TALER_ExchangePublicKeyP epub;
struct TALER_ExchangeSignatureP esig;
@@ -403,18 +45,12 @@ TEH_RESPONSE_reply_unknown_denom_pub_hash (
enum TALER_ErrorCode ec;
now = GNUNET_TIME_timestamp_get ();
- {
- struct TALER_DenominationUnknownAffirmationPS dua = {
- .purpose.size = htonl (sizeof (dua)),
- .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN),
- .timestamp = GNUNET_TIME_timestamp_hton (now),
- .h_denom_pub = *dph,
- };
-
- ec = TEH_keys_exchange_sign (&dua,
- &epub,
- &esig);
- }
+ ec = TALER_exchange_online_denomination_unknown_sign (
+ &TEH_keys_exchange_sign_,
+ now,
+ dph,
+ &epub,
+ &esig);
if (TALER_EC_NONE != ec)
{
GNUNET_break (0);
@@ -441,7 +77,7 @@ TEH_RESPONSE_reply_unknown_denom_pub_hash (
MHD_RESULT
TEH_RESPONSE_reply_expired_denom_pub_hash (
struct MHD_Connection *connection,
- const struct TALER_DenominationHash *dph,
+ const struct TALER_DenominationHashP *dph,
enum TALER_ErrorCode ec,
const char *oper)
{
@@ -450,22 +86,14 @@ TEH_RESPONSE_reply_expired_denom_pub_hash (
enum TALER_ErrorCode ecr;
struct GNUNET_TIME_Timestamp now
= GNUNET_TIME_timestamp_get ();
- struct TALER_DenominationExpiredAffirmationPS dua = {
- .purpose.size = htonl (sizeof (dua)),
- .purpose.purpose = htonl (
- TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED),
- .timestamp = GNUNET_TIME_timestamp_hton (now),
- .h_denom_pub = *dph,
- };
-
- /* strncpy would create a compiler warning */
- memcpy (dua.operation,
- oper,
- GNUNET_MIN (sizeof (dua.operation),
- strlen (oper)));
- ecr = TEH_keys_exchange_sign (&dua,
- &epub,
- &esig);
+
+ ecr = TALER_exchange_online_denomination_expired_sign (
+ &TEH_keys_exchange_sign_,
+ now,
+ dph,
+ oper,
+ &epub,
+ &esig);
if (TALER_EC_NONE != ecr)
{
GNUNET_break (0);
@@ -491,356 +119,290 @@ TEH_RESPONSE_reply_expired_denom_pub_hash (
}
-/**
- * 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
- */
MHD_RESULT
-TEH_RESPONSE_reply_coin_insufficient_funds (
+TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
struct MHD_Connection *connection,
- enum TALER_ErrorCode ec,
- const struct TALER_CoinSpendPublicKeyP *coin_pub)
+ const struct TALER_DenominationHashP *dph)
{
- struct TALER_EXCHANGEDB_TransactionList *tl;
- enum GNUNET_DB_QueryStatus qs;
- json_t *history;
-
- // FIXME: maybe start read-committed transaction here?
- // => check all callers (that they aborted already!)
- qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
- coin_pub,
- GNUNET_NO,
- &tl);
- if (0 > qs)
- {
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- NULL);
- }
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+ struct GNUNET_TIME_Timestamp now;
+ enum TALER_ErrorCode ec;
- history = TEH_RESPONSE_compile_transaction_history (coin_pub,
- tl);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- if (NULL == history)
+ 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)
{
GNUNET_break (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
- "Failed to generated proof of insufficient funds");
+ 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_array_steal ("history",
- history));
+ 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));
}
-/**
- * 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_coin_conflicting_contract (
+ struct MHD_Connection *connection,
+ enum TALER_ErrorCode ec,
+ const struct TALER_MerchantWireHashP *h_wire)
{
- 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)
+ 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)
+ );
+
+}
+
+
+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)
{
- 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 (0 >
- 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,
- 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;
- struct TALER_Amount value;
-
- value = withdraw->amount_with_fee;
- if (0 == (IA_WITHDRAW & init))
- {
- withdraw_total = value;
- init |= IA_WITHDRAW;
- }
- else
- {
- if (0 >
- 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,
- 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",
- &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 (0 >
- 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_timestamp_hton (recoup->timestamp),
- .coin_pub = recoup->coin.coin_pub,
- .reserve_pub = recoup->reserve_pub
- };
-
- TALER_amount_hton (&pc.recoup_amount,
- &recoup->value);
- if (TALER_EC_NONE !=
- TEH_keys_exchange_sign (&pc,
- &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;
- struct TALER_Amount value;
-
- value = closing->amount;
- if (0 == (IA_WITHDRAW & init))
- {
- withdraw_total = value;
- init |= IA_WITHDRAW;
- }
- else
- {
- if (0 >
- 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_timestamp_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);
- TALER_payto_hash (closing->receiver_account_details,
- &rcc.h_payto);
- if (TALER_EC_NONE !=
- TEH_keys_exchange_sign (&rcc,
- &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",
- &value),
- TALER_JSON_pack_amount ("closing_fee",
- &closing->closing_fee))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
- }
+
+ 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_CREDIT & 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)))
{
- /* We should not have gotten here, without credits no reserve
- should exist! */
GNUNET_break (0);
- json_decref (json_history);
- return NULL;
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
}
- if (0 == (IA_WITHDRAW & init))
- {
- /* did not encounter any withdraw operations, set withdraw_total to zero */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (credit_total.currency,
- &withdraw_total));
- }
- if (0 >
- 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));
+}
- return json_history;
+
+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;
+
+ 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 db2286ffa..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,41 +24,90 @@
*/
#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 "taler_error_codes.h"
#include "taler-exchange-httpd.h"
#include "taler-exchange-httpd_db.h"
-#include <gnunet/gnunet_mhd_compat.h>
+#include "taler_exchangedb_plugin.h"
/**
- * Compile the history of a reserve into a JSON object
- * and calculate the total balance.
+ * Send assertion that the given denomination key hash
+ * is unknown to us at this time.
*
- * @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 dph denomination public key hash
+ * @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_unknown_denom_pub_hash (
+ struct MHD_Connection *connection,
+ const struct TALER_DenominationHashP *dph);
/**
- * Send assertion that the given denomination key hash
- * is unknown to us at this time.
+ * Return error message indicating that a reserve had
+ * an insufficient balance for the given operation.
*
* @param connection connection to the client
- * @param dph denomination public key hash
+ * @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_unknown_denom_pub_hash (
+TEH_RESPONSE_reply_reserve_insufficient_balance (
struct MHD_Connection *connection,
- const struct TALER_DenominationHash *dph);
+ enum TALER_ErrorCode ec,
+ const struct TALER_Amount *reserve_balance,
+ const struct TALER_Amount *balance_required,
+ const struct TALER_ReservePublicKeyP *reserve_pub);
+
+/**
+ * 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 connection connection to the client
+ * @param maximum_allowed_age the balance required for the operation
+ * @return MHD result code
+ */
+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);
/**
@@ -74,12 +123,25 @@ TEH_RESPONSE_reply_unknown_denom_pub_hash (
MHD_RESULT
TEH_RESPONSE_reply_expired_denom_pub_hash (
struct MHD_Connection *connection,
- const struct TALER_DenominationHash *dph,
+ 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);
+
+
+/**
* 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
@@ -87,6 +149,7 @@ TEH_RESPONSE_reply_expired_denom_pub_hash (
*
* @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
* @return MHD result code
*/
@@ -94,20 +157,143 @@ 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_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_transfers_get.c b/src/exchange/taler-exchange-httpd_transfers_get.c
index 7ca0da974..18d96f955 100644
--- a/src/exchange/taler-exchange-httpd_transfers_get.c
+++ b/src/exchange/taler-exchange-httpd_transfers_get.c
@@ -51,7 +51,7 @@ struct AggregatedDepositDetail
/**
* Hash of the contract terms.
*/
- struct TALER_PrivateContractHash 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;
};
@@ -93,11 +99,11 @@ reply_transfer_details (struct MHD_Connection *connection,
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;
deposits = json_array ();
GNUNET_assert (NULL != deposits);
@@ -106,16 +112,12 @@ reply_transfer_details (struct MHD_Connection *connection,
NULL != wdd_pos;
wdd_pos = wdd_pos->next)
{
- dd.h_contract_terms = wdd_pos->h_contract_terms;
- dd.execution_time = GNUNET_TIME_timestamp_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,
@@ -124,6 +126,13 @@ reply_transfer_details (struct MHD_Connection *connection,
&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",
@@ -137,24 +146,21 @@ reply_transfer_details (struct MHD_Connection *connection,
"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;
- TALER_payto_hash (payto_uri,
- &wdp.h_payto);
GNUNET_CRYPTO_hash_context_finish (hash_context,
- &wdp.h_details);
+ &h_details);
{
enum TALER_ErrorCode ec;
if (TALER_EC_NONE !=
- (ec = TEH_keys_exchange_sign (&wdp,
- &pub,
- &sig)))
+ (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,
@@ -163,6 +169,8 @@ reply_transfer_details (struct MHD_Connection *connection,
}
}
+ TALER_payto_hash (payto_uri,
+ &h_payto);
return TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_OK,
@@ -173,7 +181,7 @@ reply_transfer_details (struct MHD_Connection *connection,
GNUNET_JSON_pack_data_auto ("merchant_pub",
merchant_pub),
GNUNET_JSON_pack_data_auto ("h_payto",
- &wdp.h_payto),
+ &h_payto),
GNUNET_JSON_pack_timestamp ("execution_time",
exec_time),
GNUNET_JSON_pack_array_steal ("deposits",
@@ -211,9 +219,9 @@ struct WtidTransactionContext
struct TALER_MerchantPublicKeyP merchant_pub;
/**
- * Wire fee applicable at @e exec_time.
+ * Wire fees applicable at @e exec_time.
*/
- struct TALER_Amount wire_fee;
+ struct TALER_WireFeeSet fees;
/**
* Execution time of the wire transfer
@@ -253,6 +261,31 @@ struct WtidTransactionContext
/**
+ * 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.
*
@@ -260,31 +293,101 @@ struct WtidTransactionContext
* @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 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 char *account_payto_uri,
- struct GNUNET_TIME_Timestamp exec_time,
- const struct TALER_PrivateContractHash *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;
+ 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;
+ 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' */
@@ -294,8 +397,8 @@ handle_deposit_data (void *cls,
ctx->is_valid = GNUNET_YES;
if (0 >
TALER_amount_subtract (&ctx->total,
- deposit_value,
- deposit_fee))
+ &dval,
+ &dfee))
{
GNUNET_break (0);
ctx->is_valid = GNUNET_SYSERR;
@@ -319,8 +422,8 @@ handle_deposit_data (void *cls,
}
if (0 >
TALER_amount_subtract (&delta,
- deposit_value,
- deposit_fee))
+ &dval,
+ &dfee))
{
GNUNET_break (0);
ctx->is_valid = GNUNET_SYSERR;
@@ -341,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,
@@ -399,7 +503,6 @@ get_transfer_deposits (void *cls,
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 */
@@ -455,8 +558,7 @@ get_transfer_deposits (void *cls,
ctx->exec_time,
&wire_fee_start_date,
&wire_fee_end_date,
- &ctx->wire_fee,
- &closing_fee,
+ &ctx->fees,
&wire_fee_master_sig);
GNUNET_free (wire_method);
}
@@ -476,7 +578,7 @@ get_transfer_deposits (void *cls,
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,
@@ -514,7 +616,7 @@ TEH_handler_transfers_get (struct TEH_RequestContext *rc,
if (GNUNET_OK !=
TEH_DB_run_transaction (rc->connection,
"run transfers GET",
- TEH_MT_OTHER,
+ TEH_MT_REQUEST_OTHER,
&mhd_ret,
&get_transfer_deposits,
&ctx))
@@ -526,7 +628,7 @@ TEH_handler_transfers_get (struct TEH_RequestContext *rc,
&ctx.total,
&ctx.merchant_pub,
ctx.payto_uri,
- &ctx.wire_fee,
+ &ctx.fees.wire,
ctx.exec_time,
ctx.wdd_head);
free_ctx (&ctx);
diff --git a/src/exchange/taler-exchange-httpd_wire.c b/src/exchange/taler-exchange-httpd_wire.c
deleted file mode 100644
index d378bdabc..000000000
--- a/src/exchange/taler-exchange-httpd_wire.c
+++ /dev/null
@@ -1,425 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2015-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU Affero General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-exchange-httpd_wire.c
- * @brief Handle /wire 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_wire.h"
-#include "taler_json_lib.h"
-#include "taler_mhd_lib.h"
-#include <jansson.h>
-
-
-/**
- * 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;
-
-
-/**
- * State we keep per thread to cache the /wire response.
- */
-struct WireStateHandle
-{
- /**
- * Cached JSON for /wire response.
- */
- json_t *wire_reply;
-
- /**
- * 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;
-
- /**
- * HTTP status to return with this response.
- */
- unsigned int http_status;
-
-};
-
-
-/**
- * Free memory associated with @a wsh
- *
- * @param[in] wsh wire state to destroy
- */
-static void
-destroy_wire_state (struct WireStateHandle *wsh)
-{
- json_decref (wsh->wire_reply);
- 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++;
-}
-
-
-int
-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;
- }
-}
-
-
-/**
- * Create standard JSON response format using
- * @param ec and @a detail
- *
- * @param ec error code to return
- * @param detail optional detail text to return, can be NULL
- * @return JSON response
- */
-static json_t *
-make_ec_reply (enum TALER_ErrorCode ec,
- const char *detail)
-{
- return GNUNET_JSON_PACK (
- GNUNET_JSON_pack_uint64 ("code", ec),
- GNUNET_JSON_pack_string ("hint",
- TALER_ErrorCode_get_hint (ec)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("detail", detail)));
-}
-
-
-/**
- * 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 master_sig master key signature affirming that this is a bank
- * account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS)
- */
-static void
-add_wire_account (void *cls,
- const char *payto_uri,
- const struct TALER_MasterSignatureP *master_sig)
-{
- json_t *a = cls;
-
- if (0 !=
- json_array_append_new (
- a,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("payto_uri",
- payto_uri),
- GNUNET_JSON_pack_data_auto ("master_sig",
- master_sig))))
- {
- GNUNET_break (0); /* out of memory!? */
- return;
- }
-}
-
-
-/**
- * Add information about a wire account to @a cls.
- *
- * @param cls a `json_t *` array to expand with wire account details
- * @param wire_fee the wire fee we charge
- * @param closing_fee the closing fee 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_Amount *wire_fee,
- const struct TALER_Amount *closing_fee,
- struct GNUNET_TIME_Timestamp start_date,
- struct GNUNET_TIME_Timestamp end_date,
- const struct TALER_MasterSignatureP *master_sig)
-{
- json_t *a = cls;
-
- if (0 !=
- json_array_append_new (
- a,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("wire_fee",
- wire_fee),
- TALER_JSON_pack_amount ("closing_fee",
- closing_fee),
- 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;
-
- 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->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
- wsh->wire_reply
- = make_ec_reply (TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_wire_accounts");
- return wsh;
- }
- if (0 == json_array_size (wire_accounts_array))
- {
- json_decref (wire_accounts_array);
- wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
- wsh->wire_reply
- = make_ec_reply (TALER_EC_EXCHANGE_WIRE_NO_ACCOUNTS_CONFIGURED,
- NULL);
- return wsh;
- }
- wire_fee_object = json_object ();
- GNUNET_assert (NULL != wire_fee_object);
- {
- 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)
- {
- wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
- wsh->wire_reply
- = make_ec_reply (TALER_EC_EXCHANGE_WIRE_INVALID_PAYTO_CONFIGURED,
- payto_uri);
- json_decref (wire_accounts_array);
- json_decref (wire_fee_object);
- return wsh;
- }
- if (NULL == json_object_get (wire_fee_object,
- wire_method))
- {
- json_t *a = json_array ();
-
- GNUNET_assert (NULL != a);
- qs = TEH_plugin->get_wire_fees (TEH_plugin->cls,
- wire_method,
- &add_wire_fee,
- a);
- if (0 > qs)
- {
- GNUNET_break (0);
- json_decref (a);
- json_decref (wire_fee_object);
- json_decref (wire_accounts_array);
- GNUNET_free (wire_method);
- wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
- wsh->wire_reply
- = make_ec_reply (TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_wire_fees");
- return wsh;
- }
- if (0 == json_array_size (a))
- {
- json_decref (a);
- json_decref (wire_accounts_array);
- json_decref (wire_fee_object);
- wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
- wsh->wire_reply
- = make_ec_reply (TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED,
- wire_method);
- GNUNET_free (wire_method);
- return wsh;
- }
- GNUNET_assert (0 ==
- json_object_set_new (wire_fee_object,
- wire_method,
- a));
- }
- GNUNET_free (wire_method);
- }
- }
- wsh->wire_reply = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_array_steal ("accounts",
- wire_accounts_array),
- GNUNET_JSON_pack_object_steal ("fees",
- wire_fee_object),
- GNUNET_JSON_pack_data_auto ("master_public_key",
- &TEH_master_public_key));
- wsh->http_status = MHD_HTTP_OK;
- 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++;
-}
-
-
-/**
- * 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;
-
- 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;
-}
-
-
-MHD_RESULT
-TEH_handler_wire (struct TEH_RequestContext *rc,
- const char *const args[])
-{
- struct WireStateHandle *wsh;
-
- (void) args;
- wsh = get_wire_state ();
- if (NULL == wsh)
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
- NULL);
- return TALER_MHD_reply_json (rc->connection,
- wsh->wire_reply,
- wsh->http_status);
-}
-
-
-/* end of taler-exchange-httpd_wire.c */
diff --git a/src/exchange/taler-exchange-httpd_wire.h b/src/exchange/taler-exchange-httpd_wire.h
deleted file mode 100644
index ed815a57e..000000000
--- a/src/exchange/taler-exchange-httpd_wire.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014--2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU 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.h
- * @brief Handle /wire requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_WIRE_H
-#define TALER_EXCHANGE_HTTPD_WIRE_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Clean up wire subsystem.
- */
-void
-TEH_wire_done (void);
-
-
-/**
- * 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_handler_wire()).
- */
-void
-TEH_wire_update_state (void);
-
-
-/**
- * Handle a "/wire" 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_wire (struct TEH_RequestContext *rc,
- const char *const args[]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c
deleted file mode 100644
index 53ba270ba..000000000
--- a/src/exchange/taler-exchange-httpd_withdraw.c
+++ /dev/null
@@ -1,543 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify
- it under the terms of the GNU 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_keys.h"
-
-
-/**
- * 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 withdraw_amount amount that the client requested to withdraw
- * @param rh reserve history to return
- * @return MHD result code
- */
-static MHD_RESULT
-reply_withdraw_insufficient_funds (
- struct MHD_Connection *connection,
- const struct TALER_Amount *ebalance,
- const struct TALER_Amount *withdraw_amount,
- 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_EXCHANGE_WITHDRAW_HISTORY_ERROR_INSUFFICIENT_FUNDS,
- NULL);
- 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_GENERIC_DB_INVARIANT_FAILURE,
- "reserve balance corrupt");
- }
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_JSON_pack_ec (TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS),
- TALER_JSON_pack_amount ("balance",
- &balance),
- TALER_JSON_pack_amount ("requested_amount",
- withdraw_amount),
- GNUNET_JSON_pack_array_steal ("history",
- json_history));
-}
-
-
-/**
- * Context for #withdraw_transaction.
- */
-struct WithdrawContext
-{
- /**
- * Details about the withdrawal request.
- */
- struct TALER_WithdrawRequestPS wsrd;
-
- /**
- * Blinded planchet.
- */
- void *blinded_msg;
-
- /**
- * Number of bytes in @e blinded_msg.
- */
- size_t blinded_msg_len;
-
- /**
- * Set to the resulting signed coin data to be returned to the client.
- */
- struct TALER_EXCHANGEDB_CollectableBlindcoin collectable;
-
- /**
- * KYC status for the operation.
- */
- struct TALER_EXCHANGEDB_KycStatus kyc;
-
-};
-
-
-/**
- * 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 WithdrawContext *`
- * @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
-withdraw_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct WithdrawContext *wc = cls;
- enum GNUNET_DB_QueryStatus qs;
- bool found = false;
- bool balance_ok = false;
- struct GNUNET_TIME_Timestamp now;
- uint64_t ruuid;
-
- now = GNUNET_TIME_timestamp_get ();
- wc->collectable.reserve_pub = wc->wsrd.reserve_pub;
- wc->collectable.h_coin_envelope = wc->wsrd.h_coin_envelope;
- qs = TEH_plugin->do_withdraw (TEH_plugin->cls,
- &wc->collectable,
- now,
- &found,
- &balance_ok,
- &wc->kyc,
- &ruuid);
- if (0 > qs)
- {
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "do_withdraw");
- return qs;
- }
- if (! found)
- {
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_WITHDRAW_RESERVE_UNKNOWN,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (! balance_ok)
- {
- struct TALER_EXCHANGEDB_ReserveHistory *rh;
- struct TALER_Amount balance;
- struct TALER_Amount requested_amount;
-
- TEH_plugin->rollback (TEH_plugin->cls);
- // FIXME: maybe start read-committed here?
- if (GNUNET_OK !=
- TEH_plugin->start (TEH_plugin->cls,
- "get_reserve_history on insufficient balance"))
- {
- GNUNET_break (0);
- if (NULL != mhd_ret)
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- /* The reserve does not have the required amount (actual
- * amount + withdraw fee) */
- qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
- &wc->wsrd.reserve_pub,
- &balance,
- &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_GENERIC_DB_FETCH_FAILED,
- "reserve history");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- TALER_amount_ntoh (&requested_amount,
- &wc->wsrd.amount_with_fee);
- *mhd_ret = reply_withdraw_insufficient_funds (connection,
- &balance,
- &requested_amount,
- rh);
- TEH_plugin->free_reserve_history (TEH_plugin->cls,
- rh);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
- (! wc->kyc.ok) &&
- (TALER_EXCHANGEDB_KYC_W2W == wc->kyc.type) )
- {
- /* Wallet-to-wallet payments _always_ require KYC */
- *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_ACCEPTED,
- GNUNET_JSON_pack_uint64 ("payment_target_uuid",
- wc->kyc.payment_target_uuid));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
- (! wc->kyc.ok) &&
- (TALER_EXCHANGEDB_KYC_WITHDRAW == wc->kyc.type) &&
- (! GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period)) )
- {
- /* Withdraws require KYC if above threshold */
- enum GNUNET_DB_QueryStatus qs2;
- bool below_limit;
-
- qs2 = TEH_plugin->do_withdraw_limit_check (
- TEH_plugin->cls,
- ruuid,
- GNUNET_TIME_absolute_subtract (now.abs_time,
- TEH_kyc_config.withdraw_period),
- &TEH_kyc_config.withdraw_limit,
- &below_limit);
- if (0 > qs2)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs2);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs2)
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "do_withdraw_limit_check");
- return qs2;
- }
- if (! below_limit)
- {
- TEH_plugin->rollback (TEH_plugin->cls);
- *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_ACCEPTED,
- GNUNET_JSON_pack_uint64 ("payment_target_uuid",
- wc->kyc.payment_target_uuid));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- return qs;
-}
-
-
-/**
- * Check if the @a rc is replayed and we already have an
- * answer. If so, replay the existing answer and return the
- * HTTP response.
- *
- * @param rc request context
- * @param[in,out] 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 (struct TEH_RequestContext *rc,
- struct WithdrawContext *wc,
- MHD_RESULT *mret)
-{
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
- &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)
- *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 */
- *mret = TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- TALER_JSON_pack_blinded_denom_sig ("ev_sig",
- &wc->collectable.sig));
- TALER_blinded_denom_sig_free (&wc->collectable.sig);
- return true;
-}
-
-
-MHD_RESULT
-TEH_handler_withdraw (struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[2])
-{
- struct WithdrawContext wc;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_varsize ("coin_ev",
- &wc.blinded_msg,
- &wc.blinded_msg_len),
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &wc.collectable.reserve_sig),
- GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
- &wc.collectable.denom_pub_hash),
- GNUNET_JSON_spec_end ()
- };
- enum TALER_ErrorCode ec;
- struct TEH_DenominationKey *dk;
-
- memset (&wc,
- 0,
- sizeof (wc));
- 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 (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_MERCHANT_GENERIC_RESERVE_PUB_MALFORMED,
- args[0]);
- }
-
- {
- 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;
- }
- {
- MHD_RESULT mret;
- struct TEH_KeyStateHandle *ksh;
-
- ksh = TEH_keys_get_state ();
- if (NULL == ksh)
- {
- if (! check_request_idempotent (rc,
- &wc,
- &mret))
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- NULL);
- }
- GNUNET_JSON_parse_free (spec);
- return mret;
- }
- dk = TEH_keys_denomination_by_hash2 (ksh,
- &wc.collectable.denom_pub_hash,
- NULL,
- NULL);
- if (NULL == dk)
- {
- if (! check_request_idempotent (rc,
- &wc,
- &mret))
- {
- GNUNET_JSON_parse_free (spec);
- return TEH_RESPONSE_reply_unknown_denom_pub_hash (
- rc->connection,
- &wc.collectable.denom_pub_hash);
- }
- GNUNET_JSON_parse_free (spec);
- 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 (rc,
- &wc,
- &mret))
- {
- GNUNET_JSON_parse_free (spec);
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- rc->connection,
- &wc.collectable.denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
- "WITHDRAW");
- }
- GNUNET_JSON_parse_free (spec);
- 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! */
- GNUNET_JSON_parse_free (spec);
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- rc->connection,
- &wc.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 (rc,
- &wc,
- &mret))
- {
- GNUNET_JSON_parse_free (spec);
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- rc->connection,
- &wc.collectable.denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
- "WITHDRAW");
- }
- GNUNET_JSON_parse_free (spec);
- return mret;
- }
- }
-
- if (0 >
- TALER_amount_add (&wc.collectable.amount_with_fee,
- &dk->meta.value,
- &dk->meta.fee_withdraw))
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
- NULL);
- }
- TALER_amount_hton (&wc.wsrd.amount_with_fee,
- &wc.collectable.amount_with_fee);
-
- // FIXME: move this logic into libtalerutil!
- /* verify signature! */
- wc.wsrd.purpose.size
- = htonl (sizeof (wc.wsrd));
- wc.wsrd.purpose.purpose
- = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
- wc.wsrd.h_denomination_pub
- = wc.collectable.denom_pub_hash;
- TALER_coin_ev_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,
- &wc.collectable.reserve_sig.eddsa_signature,
- &wc.wsrd.reserve_pub.eddsa_pub))
- {
- TALER_LOG_WARNING (
- "Client supplied invalid signature for withdraw request\n");
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
- NULL);
- }
-
- /* Sign before transaction! */
- ec = TALER_EC_NONE;
- wc.collectable.sig
- = TEH_keys_denomination_sign (&wc.collectable.denom_pub_hash,
- wc.blinded_msg,
- wc.blinded_msg_len,
- &ec);
- if (TALER_EC_NONE != ec)
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_ec (rc->connection,
- ec,
- NULL);
- }
-
- /* run transaction */
- {
- MHD_RESULT mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (rc->connection,
- "run withdraw",
- TEH_MT_WITHDRAW,
- &mhd_ret,
- &withdraw_transaction,
- &wc))
- {
- /* Even if #withdraw_transaction() failed, it may have created a signature
- (or we might have done it optimistically above). */
- TALER_blinded_denom_sig_free (&wc.collectable.sig);
- GNUNET_JSON_parse_free (spec);
- return mhd_ret;
- }
- }
-
- /* Clean up and send back final response */
- GNUNET_JSON_parse_free (spec);
-
- {
- MHD_RESULT ret;
-
- ret = TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- TALER_JSON_pack_blinded_denom_sig ("ev_sig",
- &wc.collectable.sig));
- TALER_blinded_denom_sig_free (&wc.collectable.sig);
- return ret;
- }
-}
-
-
-/* end of taler-exchange-httpd_withdraw.c */
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 011da6b57..9724b41fc 100644
--- a/src/exchange/taler-exchange-transfer.c
+++ b/src/exchange/taler-exchange-transfer.c
@@ -264,7 +264,7 @@ shutdown_task (void *cls)
/**
- * Parse the configuration for wirewatch.
+ * Parse the configuration for taler-exchange-transfer.
*
* @return #GNUNET_OK on success
*/
@@ -406,25 +406,17 @@ batch_done (void)
* except for irrecoverable errors.
*
* @param cls `struct WirePrepareData` we are working on
- * @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 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_Timestamp wire_timestamp)
+ const struct TALER_BANK_TransferResponse *tr)
{
struct WirePrepareData *wpd = cls;
enum GNUNET_DB_QueryStatus qs;
- (void) row_id;
- (void) wire_timestamp;
wpd->eh = NULL;
- switch (http_status_code)
+ switch (tr->http_status)
{
case MHD_HTTP_OK:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -435,11 +427,12 @@ wire_confirm_cb (void *cls,
/* continued below */
break;
case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_CONFLICT:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Wire transaction %llu failed: %u/%d\n",
(unsigned long long) wpd->row_id,
- http_status_code,
- ec);
+ tr->http_status,
+ tr->ec);
qs = db_plugin->wire_prepare_data_mark_failed (db_plugin->cls,
wpd->row_id);
/* continued below */
@@ -456,7 +449,7 @@ wire_confirm_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Wire transfer %llu failed (%u), trying again\n",
(unsigned long long) wpd->row_id,
- http_status_code);
+ tr->http_status);
wpd->eh = TALER_BANK_transfer (ctx,
wpd->wa->auth,
&wpd[1],
@@ -468,8 +461,8 @@ wire_confirm_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Wire transaction %llu failed: %u/%d\n",
(unsigned long long) wpd->row_id,
- http_status_code,
- ec);
+ tr->http_status,
+ tr->ec);
cleanup_wpd ();
db_plugin->rollback (db_plugin->cls);
global_ret = EXIT_FAILURE;
@@ -479,8 +472,8 @@ wire_confirm_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Wire transfer %llu failed: %u/%d\n",
(unsigned long long) wpd->row_id,
- http_status_code,
- ec);
+ tr->http_status,
+ tr->ec);
db_plugin->rollback (db_plugin->cls);
cleanup_wpd ();
global_ret = EXIT_FAILURE;
@@ -563,9 +556,9 @@ wire_prepare_cb (void *cls,
}
wpd = GNUNET_malloc (sizeof (struct WirePrepareData)
+ buf_size);
- memcpy (&wpd[1],
- buf,
- buf_size);
+ GNUNET_memcpy (&wpd[1],
+ buf,
+ buf_size);
wpd->buf_size = buf_size;
wpd->row_id = rowid;
GNUNET_CONTAINER_DLL_insert (wpd_head,
@@ -582,7 +575,7 @@ wire_prepare_cb (void *cls,
GNUNET_break (0);
cleanup_wpd ();
db_plugin->rollback (db_plugin->cls);
- global_ret = EXIT_NOTCONFIGURED;
+ global_ret = EXIT_NO_RESTART;
GNUNET_SCHEDULER_shutdown ();
return;
}
@@ -715,7 +708,7 @@ run_transfers (void *cls)
}
else
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"No more pending wire transfers, going idle\n");
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_delayed (transfer_idle_sleep_interval,
diff --git a/src/exchange/taler-exchange-wirewatch.c b/src/exchange/taler-exchange-wirewatch.c
index 6b63de76a..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--2021 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
@@ -43,111 +42,111 @@
#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;
-
- /**
- * Plugins are kept in a DLL.
- */
- struct WireAccount *prev;
+static const struct TALER_EXCHANGEDB_AccountInfo *ai;
- /**
- * Information about this account.
- */
- const struct TALER_EXCHANGEDB_AccountInfo *ai;
+/**
+ * Active request for history.
+ */
+static struct TALER_BANK_CreditHistoryHandle *hh;
- /**
- * Active request for history.
- */
- struct TALER_BANK_CreditHistoryHandle *hh;
+/**
+ * Set to true if the request for history did actually
+ * return transaction items.
+ */
+static bool hh_returned_data;
- /**
- * Until when is processing this wire plugin delayed?
- */
- struct GNUNET_TIME_Absolute delayed_until;
+/**
+ * Set to true if the request for history did not
+ * succeed because the account was unknown.
+ */
+static bool hh_account_404;
- /**
- * Encoded offset in the wire transfer list from where
- * to start the next query with the bank.
- */
- uint64_t batch_start;
+/**
+ * When did we start the last @e hh request?
+ */
+static struct GNUNET_TIME_Absolute hh_start_time;
- /**
- * Latest row offset seen in this transaction, becomes
- * the new #batch_start upon commit.
- */
- uint64_t latest_row_off;
+/**
+ * Until when is processing this wire plugin delayed?
+ */
+static struct GNUNET_TIME_Absolute delayed_until;
- /**
- * Offset where our current shard begins (inclusive).
- */
- uint64_t shard_start;
+/**
+ * Encoded offset in the wire transfer list from where
+ * to start the next query with the bank.
+ */
+static uint64_t batch_start;
- /**
- * Offset where our current shard ends (exclusive).
- */
- uint64_t shard_end;
+/**
+ * Latest row offset seen in this transaction, becomes
+ * the new #batch_start upon commit.
+ */
+static uint64_t latest_row_off;
- /**
- * When did we start with the shard?
- */
- struct GNUNET_TIME_Absolute shard_start_time;
+/**
+ * Offset where our current shard begins (inclusive).
+ */
+static uint64_t shard_start;
- /**
- * Name of our job in the shard table.
- */
- char *job_name;
+/**
+ * Offset where our current shard ends (exclusive).
+ */
+static uint64_t shard_end;
- /**
- * How many transactions do we retrieve per batch?
- */
- unsigned int batch_size;
+/**
+ * When did we start with the shard?
+ */
+static struct GNUNET_TIME_Absolute shard_start_time;
- /**
- * How much do we incremnt @e batch_size on success?
- */
- unsigned int batch_thresh;
+/**
+ * For how long did we lock the shard?
+ */
+static struct GNUNET_TIME_Absolute shard_end_time;
- /**
- * How many transactions did we see in the current batch?
- */
- unsigned int current_batch_size;
+/**
+ * How long did we take to finish the last shard
+ * for this account?
+ */
+static struct GNUNET_TIME_Relative shard_delay;
- /**
- * Should we delay the next request to the wire plugin a bit? Set to
- * false if we actually did some work.
- */
- bool delay;
+/**
+ * How long did we take to finish the last shard
+ * for this account?
+ */
+static struct GNUNET_TIME_Relative longpoll_timeout;
- /**
- * Did we start a transaction yet?
- */
- bool started_transaction;
+/**
+ * Name of our job in the shard table.
+ */
+static char *job_name;
-};
+/**
+ * How many transactions do we retrieve per batch?
+ */
+static unsigned int batch_size;
+/**
+ * How much do we increment @e batch_size on success?
+ */
+static unsigned int batch_thresh;
/**
- * Head of list of loaded wire plugins.
+ * 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_head;
+static bool progress;
/**
- * Tail of list of loaded wire plugins.
+ * Did we start a transaction yet?
*/
-static struct WireAccount *wa_tail;
+static bool started_transaction;
/**
- * Wire account we are currently processing. This would go away
- * if we ever start processing all accounts in parallel.
+ * Is this shard still open for processing.
*/
-static struct WireAccount *wa_pos;
+static bool shard_open;
/**
* Handle to the context for interacting with the bank.
@@ -178,9 +177,9 @@ static struct TALER_EXCHANGEDB_Plugin *db_plugin;
static struct GNUNET_TIME_Relative wirewatch_idle_sleep_interval;
/**
- * How long did we take to finish the last shard?
+ * How long do we sleep on serialization conflicts?
*/
-static struct GNUNET_TIME_Relative shard_delay;
+static struct GNUNET_TIME_Relative wirewatch_conflict_sleep_interval;
/**
* Modulus to apply to group shards. The shard size must ultimately be a
@@ -194,6 +193,10 @@ static unsigned int shard_size = MAXIMUM_BATCH_SIZE;
*/
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
@@ -207,10 +210,20 @@ static int global_ret;
static int test_mode;
/**
+ * Should we ignore if the bank does not know our bank
+ * account?
+ */
+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.
@@ -220,32 +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);
- if (wa->started_transaction)
- {
- db_plugin->rollback (db_plugin->cls);
- wa->started_transaction = false;
- }
- // FIXME: delete shard lock here (#7124)
- GNUNET_free (wa->job_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);
@@ -273,28 +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 (! ai->credit_enabled)
+ if (! in_ai->credit_enabled)
return; /* not enabled for us, skip */
- wa = GNUNET_new (struct WireAccount);
- wa->ai = ai;
- GNUNET_asprintf (&wa->job_name,
+ 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_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;
+ }
+ ai = in_ai;
+ GNUNET_asprintf (&job_name,
"wirewatch-%s",
ai->section_name);
- wa->batch_size = MAXIMUM_BATCH_SIZE;
- if (0 != shard_size % wa->batch_size)
- wa->batch_size = shard_size;
- GNUNET_CONTAINER_DLL_insert (wa_head,
- wa_tail,
- wa);
+ batch_size = MAXIMUM_BATCH_SIZE;
+ if (0 != shard_size % batch_size)
+ batch_size = shard_size;
}
@@ -332,342 +353,438 @@ exchange_serve_process_config (void)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No wire accounts configured for credit!\n");
- TALER_EXCHANGEDB_plugin_unload (db_plugin);
- db_plugin = NULL;
return GNUNET_SYSERR;
}
TALER_EXCHANGEDB_find_accounts (&add_account_cb,
NULL);
- GNUNET_assert (NULL != wa_head);
+ if (NULL == ai)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No accounts enabled for credit!\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return GNUNET_SYSERR;
+ }
return GNUNET_OK;
}
/**
- * 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);
/**
- * We encountered a serialization error.
- * Rollback the transaction and try again
+ * Continue with the credit history of the shard.
*
- * @param wa account we are transacting on
+ * @param cls NULL
*/
static void
-handle_soft_error (struct WireAccount *wa)
+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);
- wa->started_transaction = false;
- if (1 < wa->batch_size)
+ started_transaction = false;
+ if (1 < batch_size)
{
- wa->batch_thresh = wa->batch_size;
- wa->batch_size /= 2;
+ 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) wa->batch_size);
+ (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 (&find_transfers,
+ task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
NULL);
}
/**
- * We are done with a shard, move on to the next one.
- *
- * @param wa wire account for which we completed a shard
+ * Schedule the #lock_shard() operation.
*/
static void
-shard_completed (struct WireAccount *wa)
+schedule_transfers (void)
{
- /* transaction success, update #last_row_off */
- wa->batch_start = wa->latest_row_off;
- if (wa->batch_size < MAXIMUM_BATCH_SIZE)
- {
- int delta;
-
- delta = ((int) wa->batch_thresh - (int) wa->batch_size) / 4;
- if (delta < 0)
- delta = -delta;
- wa->batch_size = GNUNET_MIN (MAXIMUM_BATCH_SIZE,
- wa->batch_size + delta + 1);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Increasing batch size to %llu\n",
- (unsigned long long) wa->batch_size);
- }
- if (wa->delay)
- {
- 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);
- }
+ 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);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Will look for more transfers in %s\n",
- GNUNET_STRINGS_relative_time_to_string (
- GNUNET_TIME_absolute_get_remaining (wa_pos->delayed_until),
- GNUNET_YES));
- task = GNUNET_SCHEDULER_add_at (wa_pos->delayed_until,
- &find_transfers,
+ task = GNUNET_SCHEDULER_add_at (delayed_until,
+ &lock_shard,
NULL);
}
/**
- * We are finished with the current shard. Update the database, marking the
- * shard as finished.
- *
- * @param wa wire account to commit for
- * @return true on success
+ * 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 bool
-mark_shard_done (struct WireAccount *wa)
+static void
+transaction_completed (void)
{
- enum GNUNET_DB_QueryStatus qs;
-
- if (wa->shard_end > wa->latest_row_off)
- return false; /* actually, not done! */
- /* shard is complete, mark this as well */
- qs = db_plugin->complete_shard (db_plugin->cls,
- wa->job_name,
- wa->shard_start,
- wa->shard_end);
- switch (qs)
+ if ( (batch_start + batch_size ==
+ latest_row_off) &&
+ (batch_size < MAXIMUM_BATCH_SIZE) )
{
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- db_plugin->rollback (db_plugin->cls);
- GNUNET_SCHEDULER_shutdown ();
- return false;
- 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 (wa);
- return false;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* already existed, ok, let's just continue */
- break;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* normal case */
- shard_delay = GNUNET_TIME_absolute_get_duration (wa->shard_start_time);
+ /* The current batch size worked without serialization
+ issues, and we are allowed to grow. Do so slowly. */
+ int delta;
- break;
+ 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);
}
- return true;
-}
-
-/**
- * We are finished with the current transaction, try
- * to commit and then schedule the next iteration.
- *
- * @param wa wire account to commit for
- */
-static void
-do_commit (struct WireAccount *wa)
-{
- enum GNUNET_DB_QueryStatus qs;
-
- wa->started_transaction = false;
- mark_shard_done (wa);
- qs = db_plugin->commit (db_plugin->cls);
- switch (qs)
+ if ( (! progress) && test_mode)
{
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
+ /* 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;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- /* reduce transaction size to reduce rollback probability */
- handle_soft_error (wa);
- return;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* normal case */
- break;
}
- shard_completed (wa);
+ 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 ();
}
/**
- * Callbacks of this type are used to serve the result of asking
- * the bank for the transaction history.
+ * We got incoming transaction details from the bank. Add them
+ * to the database.
*
- * @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
+ * @param details array of transaction details
+ * @param details_length length of the @a details array
*/
-static enum GNUNET_GenericReturnValue
-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)
+static void
+process_reply (const struct TALER_BANK_CreditDetails *details,
+ unsigned int details_length)
{
- struct WireAccount *wa = cls;
enum GNUNET_DB_QueryStatus qs;
+ bool shard_done;
+ uint64_t lroff = latest_row_off;
- (void) json;
- if (NULL == details)
+ if (0 == details_length)
+ {
+ /* Server should have used 204, not 200! */
+ GNUNET_break_op (0);
+ transaction_completed ();
+ return;
+ }
+ hh_returned_data = true;
+ /* check serial IDs for range constraints */
+ for (unsigned int i = 0; i<details_length; i++)
{
- wa->hh = NULL;
- if (TALER_EC_NONE != ec)
+ 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;
}
- else
+ if (cd->serial_id > shard_end)
{
+ /* we are *past* the current shard (likely because the serial_id of the
+ shard_end happens to not exist in the DB). So commit and stop this
+ iteration! */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "History response complete\n");
+ "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 (wa->started_transaction)
+ lroff = cd->serial_id;
+ }
+ if (0 != details_length)
+ {
+ enum GNUNET_DB_QueryStatus qss[details_length];
+ struct TALER_EXCHANGEDB_ReserveInInfo reserves[details_length];
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Importing %u transactions\n",
+ details_length);
+ for (unsigned int i = 0; i<details_length; i++)
+ {
+ const struct TALER_BANK_CreditDetails *cd = &details[i];
+ struct TALER_EXCHANGEDB_ReserveInInfo *res = &reserves[i];
+
+ res->reserve_pub = &cd->reserve_pub;
+ res->balance = &cd->amount;
+ res->execution_time = cd->execution_date;
+ res->sender_account_details = cd->debit_account_uri;
+ res->exchange_account_name = ai->section_name;
+ res->wire_reference = cd->serial_id;
+ }
+ qs = db_plugin->reserves_in_insert (db_plugin->cls,
+ reserves,
+ details_length,
+ qss);
+ switch (qs)
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "End of list. Committing progress!\n");
- do_commit (wa);
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got DB soft error for reserves_in_insert (%u). Rolling back.\n",
+ details_length);
+ handle_soft_error ();
+ return;
+ default:
+ break;
}
- else
+ for (unsigned int i = 0; i<details_length; i++)
{
- if ( (wa->delay) &&
- (test_mode) &&
- (NULL == wa->next) )
+ const struct TALER_BANK_CreditDetails *cd = &details[i];
+
+ switch (qss[i])
{
- 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;
- }
- else
- {
- shard_completed (wa);
+ 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;
}
}
- return GNUNET_OK; /* will be ignored anyway */
}
- if (serial_id < wa->latest_row_off)
+
+ latest_row_off = lroff;
+ shard_done = (shard_end <= latest_row_off);
+ if (shard_done)
{
- /* we are done with the current shard, commit and stop this iteration! */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Serial ID %llu not monotonic (got %llu before). Failing!\n",
- (unsigned long long) serial_id,
- (unsigned long long) wa->latest_row_off);
- if (wa->started_transaction)
+ /* shard is complete, mark this as well */
+ qs = db_plugin->complete_shard (db_plugin->cls,
+ job_name,
+ shard_start,
+ shard_end);
+ switch (qs)
{
- wa->started_transaction = false;
- db_plugin->rollback (db_plugin->cls);
+ 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;
}
- GNUNET_SCHEDULER_shutdown ();
- wa->hh = NULL;
- return GNUNET_SYSERR;
+ shard_delay = GNUNET_TIME_absolute_get_duration (shard_start_time);
+ shard_open = false;
+ transaction_completed ();
+ return;
}
- if (serial_id > wa->shard_end)
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
+ NULL);
+}
+
+
+/**
+ * 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)
{
- /* we are done with the current shard, 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) serial_id,
- (unsigned long long) wa->shard_end);
- wa->latest_row_off = serial_id - 1;
- wa->delay = false;
- if (wa->started_transaction)
- {
- do_commit (wa);
- }
- else
+ 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)
{
- if (mark_shard_done (wa))
- shard_completed (wa);
+ transaction_completed ();
+ return;
}
- wa->hh = NULL;
- return GNUNET_SYSERR;
+ 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 (! wa->started_transaction)
+ if (! exit_on_error)
{
- if (GNUNET_OK !=
- db_plugin->start_read_committed (db_plugin->cls,
- "wirewatch check for incoming wire transfers"))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to start database transaction!\n");
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- wa->hh = NULL;
- return GNUNET_SYSERR;
- }
- wa_pos->shard_start_time = GNUNET_TIME_absolute_get ();
- wa->started_transaction = true;
+ transaction_completed ();
+ return;
}
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Adding wire transfer over %s with (hashed) subject `%s'\n",
- TALER_amount2s (&details->amount),
- TALER_B2S (&details->reserve_pub));
- /* FIXME-PERFORMANCE: Consider using Postgres multi-valued insert here,
- for up to 15x speed-up according to
- https://dba.stackexchange.com/questions/224989/multi-row-insert-vs-transactional-single-row-inserts#225006
- (Note: this may require changing both the
- plugin API as well as modifying how this function is called.) */
- qs = db_plugin->reserves_in_insert (db_plugin->cls,
- &details->reserve_pub,
- &details->amount,
- details->execution_date,
- details->debit_account_uri,
- wa->ai->section_name,
- serial_id);
- switch (qs)
+ 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)
{
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- db_plugin->rollback (db_plugin->cls);
- wa->started_transaction = false;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start request for account history!\n");
+ global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
- wa->hh = NULL;
- return GNUNET_SYSERR;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Got DB soft error for reserves_in_insert. Rolling back.\n");
- handle_soft_error (wa);
- wa->hh = NULL;
- return GNUNET_SYSERR;
- 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;
+ return;
}
- wa->delay = false;
- 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)
{
enum GNUNET_DB_QueryStatus qs;
- unsigned int limit;
+ struct GNUNET_TIME_Relative delay;
+ uint64_t last_shard_start = shard_start;
+ uint64_t last_shard_end = shard_end;
(void) cls;
task = NULL;
@@ -680,100 +797,122 @@ find_transfers (void *cls)
GNUNET_SCHEDULER_shutdown ();
return;
}
- wa_pos->delay = true;
- wa_pos->current_batch_size = 0; /* reset counter */
- if (wa_pos->shard_end <= wa_pos->batch_start)
+ if ( (shard_open) &&
+ (GNUNET_TIME_absolute_is_future (shard_end_time)) )
{
- uint64_t start;
- uint64_t end;
- struct GNUNET_TIME_Relative delay;
- /* advance to next 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);
- qs = db_plugin->begin_shard (db_plugin->cls,
- wa_pos->job_name,
- 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 */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Serialization error tying to obtain shard, will try again in %s!\n",
- GNUNET_STRINGS_relative_time_to_string (
- wirewatch_idle_sleep_interval,
- GNUNET_YES));
- task = GNUNET_SCHEDULER_add_delayed (wirewatch_idle_sleep_interval,
- &find_transfers,
- NULL);
- return;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "No shard available, will try again in %s!\n",
- GNUNET_STRINGS_relative_time_to_string (
- wirewatch_idle_sleep_interval,
- GNUNET_YES));
- task = GNUNET_SCHEDULER_add_delayed (wirewatch_idle_sleep_interval,
- &find_transfers,
- NULL);
- return;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- wa_pos->shard_start = start;
- wa_pos->shard_end = end;
- wa_pos->batch_start = start;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Starting with shard at [%llu,%llu) locked for %s\n",
- (unsigned long long) start,
- (unsigned long long) end,
- GNUNET_STRINGS_relative_time_to_string (delay,
- GNUNET_YES));
- break;
- }
+ progress = false;
+ batch_start = latest_row_off;
+ task = GNUNET_SCHEDULER_add_now (&continue_with_shard,
+ NULL);
+ return;
}
-
- limit = GNUNET_MIN (wa_pos->batch_size,
- wa_pos->shard_end - wa_pos->batch_start);
- GNUNET_assert (NULL == wa_pos->hh);
- wa_pos->latest_row_off = wa_pos->batch_start;
- wa_pos->hh = TALER_BANK_credit_history (ctx,
- wa_pos->ai->auth,
- wa_pos->batch_start,
- limit,
- test_mode
- ? GNUNET_TIME_UNIT_ZERO
- : LONGPOLL_TIMEOUT,
- &history_cb,
- wa_pos);
- if (NULL == wa_pos->hh)
+ 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)
{
+ case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to start request for account history!\n");
- if (wa_pos->started_transaction)
- {
- db_plugin->rollback (db_plugin->cls);
- wa_pos->started_transaction = false;
- }
+ "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 */
+ {
+ 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);
+ }
+ 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;
+ }
+ shard_end_time = GNUNET_TIME_relative_to_absolute (delay);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "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_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);
}
@@ -796,27 +935,26 @@ run (void *cls,
(void) cfgfile;
cfg = c;
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ cls);
if (GNUNET_OK !=
exchange_serve_process_config ())
{
global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
return;
}
- wa_pos = wa_head;
- GNUNET_assert (NULL != wa_pos);
- 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;
}
-
- task = GNUNET_SCHEDULER_add_now (&find_transfers,
- NULL);
+ rc = GNUNET_CURL_gnunet_rc_create (ctx);
+ schedule_transfers ();
}
@@ -832,6 +970,24 @@ 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",
@@ -853,6 +1009,7 @@ main (int argc,
};
enum GNUNET_GenericReturnValue ret;
+ longpoll_timeout = LONGPOLL_TIMEOUT;
if (GNUNET_OK !=
GNUNET_STRINGS_get_utf8_args (argc, argv,
&argc, &argv))
diff --git a/src/exchange/test_taler_exchange_httpd.conf b/src/exchange/test_taler_exchange_httpd.conf
index 2adee5053..7e7ff8b45 100644
--- a/src/exchange/test_taler_exchange_httpd.conf
+++ b/src/exchange/test_taler_exchange_httpd.conf
@@ -13,6 +13,8 @@ TINY_AMOUNT = EUR:0.01
[exchange]
+AML_THRESHOLD = EUR:1000000
+
# Directory with our terms of service.
TERMS_DIR = ../../contrib/tos
@@ -67,10 +69,10 @@ ENABLE_CREDIT = YES
WIRE_GATEWAY_AUTH_METHOD = basic
USERNAME = Exchange
PASSWORD = x
-WIRE_GATEWAY_URL = "http://localhost:8082/3/"
+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
@@ -79,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
@@ -90,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
@@ -101,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.sh b/src/exchange/test_taler_exchange_httpd.sh
index e8dc46af8..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,6 +23,8 @@
# Clear environment from variables that override config.
unset XDG_DATA_HOME
unset XDG_CONFIG_HOME
+
+set -eu
#
echo -n "Launching exchange ..."
PREFIX=
@@ -30,7 +32,7 @@ PREFIX=
#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
+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 &
diff --git a/src/exchange/test_taler_exchange_unix.conf b/src/exchange/test_taler_exchange_unix.conf
index b9387f603..e96bfba3f 100644
--- a/src/exchange/test_taler_exchange_unix.conf
+++ b/src/exchange/test_taler_exchange_unix.conf
@@ -70,7 +70,7 @@ 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
@@ -79,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
@@ -90,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
@@ -101,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 aea9a74e4..6e67fadb1 100644
--- a/src/exchangedb/.gitignore
+++ b/src/exchangedb/.gitignore
@@ -1,7 +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 3145c3c09..fd993f968 100644
--- a/src/exchangedb/Makefile.am
+++ b/src/exchangedb/Makefile.am
@@ -14,22 +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 = \
- benchmark-0000.sql \
benchmark-0001.sql \
- exchange-0000.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 \
- irbt_callbacks.c \
- lrbt_callbacks.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
@@ -39,19 +88,203 @@ 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/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
- -lpq \
- -lpthread \
-lgnunetpq \
-lgnunetutil \
-ljansson \
+ -lpq \
$(XLIB)
lib_LTLIBRARIES = \
@@ -73,12 +306,19 @@ libtalerexchangedb_la_LDFLAGS = \
check_PROGRAMS = \
- test-exchangedb-postgres \
- bench-db-postgres
+ 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-postgres
+ $(check_SCRIPTS) \
+ $(check_PROGRAMS)
test_exchangedb_postgres_SOURCES = \
test_exchangedb.c
@@ -102,5 +342,58 @@ bench_db_postgres_LDADD = \
-lgnunetutil \
$(XLIB)
+perf_reserves_in_insert_postgres_SOURCES = \
+ perf_reserves_in_insert.c
+perf_reserves_in_insert_postgres_LDADD = \
+ libtalerexchangedb.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ -ljansson \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lm \
+ $(XLIB)
+
+perf_select_refunds_by_coin_postgres_SOURCES = \
+ perf_select_refunds_by_coin.c
+perf_select_refunds_by_coin_postgres_LDADD = \
+ libtalerexchangedb.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ -ljansson \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lm \
+ $(XLIB)
+
+perf_get_link_data_postgres_SOURCES = \
+ perf_get_link_data.c
+perf_get_link_data_postgres_LDADD = \
+ libtalerexchangedb.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ -ljansson \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lm \
+ $(XLIB)
+
+perf_deposits_get_ready_postgres_SOURCES = \
+ perf_deposits_get_ready.c
+perf_deposits_get_ready_postgres_LDADD = \
+ libtalerexchangedb.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ -ljansson \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lm \
+ $(XLIB)
+
+
EXTRA_test_exchangedb_postgres_DEPENDENCIES = \
libtaler_plugin_exchangedb_postgres.la
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
index 837ae41e2..d51cf9175 100644
--- a/src/exchangedb/bench-db-postgres.conf
+++ b/src/exchangedb/bench-db-postgres.conf
@@ -8,3 +8,7 @@ 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
index a8dbfbfa5..302d23062 100644
--- a/src/exchangedb/bench_db.c
+++ b/src/exchangedb/bench_db.c
@@ -51,32 +51,28 @@ prepare (struct GNUNET_PQ_Context *conn)
"(hc"
",expiration_date"
") VALUES "
- "($1, $2);",
- 2),
+ "($1, $2);"),
/* Used in #postgres_iterate_denomination_info() */
GNUNET_PQ_make_prepare (
"bm_select",
"SELECT"
" expiration_date"
" FROM benchmap"
- " WHERE hc=$1;",
- 1),
+ " WHERE hc=$1;"),
GNUNET_PQ_make_prepare (
"bhm_insert",
"INSERT INTO benchhmap "
"(hc"
",expiration_date"
") VALUES "
- "($1, $2);",
- 2),
+ "($1, $2);"),
/* Used in #postgres_iterate_denomination_info() */
GNUNET_PQ_make_prepare (
"bhm_select",
"SELECT"
" expiration_date"
" FROM benchhmap"
- " WHERE hc=$1;",
- 1),
+ " WHERE hc=$1;"),
GNUNET_PQ_make_prepare (
"bem_insert",
"INSERT INTO benchemap "
@@ -84,16 +80,14 @@ prepare (struct GNUNET_PQ_Context *conn)
",ihc"
",expiration_date"
") VALUES "
- "($1, $2, $3);",
- 3),
+ "($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;",
- 2),
+ " WHERE ihc=$1 AND hc=$2;"),
GNUNET_PQ_PREPARED_STATEMENT_END
};
enum GNUNET_GenericReturnValue ret;
@@ -175,9 +169,9 @@ bem_insert (struct GNUNET_PQ_Context *conn,
GNUNET_CRYPTO_hash (&b,
sizeof (b),
&hc);
- memcpy (&ihc,
- &hc,
- sizeof (ihc));
+ GNUNET_memcpy (&ihc,
+ &hc,
+ sizeof (ihc));
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (&hc),
@@ -271,9 +265,9 @@ bem_select (struct GNUNET_PQ_Context *conn,
GNUNET_CRYPTO_hash (&b,
sizeof (b),
&hc);
- memcpy (&ihc,
- &hc,
- sizeof (ihc));
+ GNUNET_memcpy (&ihc,
+ &hc,
+ sizeof (ihc));
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint32 (&ihc),
diff --git a/src/exchangedb/benchmark-0000.sql b/src/exchangedb/benchmark-0000.sql
deleted file mode 100644
index 116f409b7..000000000
--- a/src/exchangedb/benchmark-0000.sql
+++ /dev/null
@@ -1,293 +0,0 @@
--- LICENSE AND COPYRIGHT
---
--- Copyright (C) 2010 Hubert depesz Lubaczewski
---
--- This program is distributed under the (Revised) BSD License:
--- L<http://www.opensource.org/licenses/bsd-license.php>
---
--- Redistribution and use in source and binary forms, with or without
--- modification, are permitted provided that the following conditions
--- are met:
---
--- * Redistributions of source code must retain the above copyright
--- notice, this list of conditions and the following disclaimer.
---
--- * Redistributions in binary form must reproduce the above copyright
--- notice, this list of conditions and the following disclaimer in the
--- documentation and/or other materials provided with the distribution.
---
--- * Neither the name of Hubert depesz Lubaczewski's Organization
--- nor the names of its contributors may be used to endorse or
--- promote products derived from this software without specific
--- prior written permission.
---
--- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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.
---
--- Code origin: https://gitlab.com/depesz/Versioning/blob/master/install.versioning.sql
---
---
--- # NAME
---
--- **Versioning** - simplistic take on tracking and applying changes to databases.
---
--- # DESCRIPTION
---
--- This project strives to provide simple way to manage changes to
--- database.
---
--- Instead of making changes on development server, then finding
--- differences between production and development, deciding which ones
--- should be installed on production, and finding a way to install them -
--- you start with writing diffs themselves!
---
--- # INSTALLATION
---
--- To install versioning simply run install.versioning.sql in your database
--- (all of them: production, stage, test, devel, ...).
---
--- # USAGE
---
--- In your files with patches to database, put whole logic in single
--- transaction, and use \_v.\* functions - usually \_v.register_patch() at
--- least to make sure everything is OK.
---
--- For example. Let's assume you have patch files:
---
--- ## 0001.sql:
---
--- ```
--- create table users (id serial primary key, username text);
--- ```
---
--- ## 0002.sql:
---
--- ```
--- insert into users (username) values ('depesz');
--- ```
--- To change it to use versioning you would change the files, to this
--- state:
---
--- 0000.sql:
---
--- ```
--- BEGIN;
--- select _v.register_patch('000-base', NULL, NULL);
--- create table users (id serial primary key, username text);
--- COMMIT;
--- ```
---
--- ## 0002.sql:
---
--- ```
--- BEGIN;
--- select _v.register_patch('001-users', ARRAY['000-base'], NULL);
--- insert into users (username) values ('depesz');
--- COMMIT;
--- ```
---
--- This will make sure that patch 001-users can only be applied after
--- 000-base.
---
--- # AVAILABLE FUNCTIONS
---
--- ## \_v.register_patch( TEXT )
---
--- Registers named patch, or dies if it is already registered.
---
--- Returns integer which is id of patch in \_v.patches table - only if it
--- succeeded.
---
--- ## \_v.register_patch( TEXT, TEXT[] )
---
--- Same as \_v.register_patch( TEXT ), but checks is all given patches (given as
--- array in second argument) are already registered.
---
--- ## \_v.register_patch( TEXT, TEXT[], TEXT[] )
---
--- Same as \_v.register_patch( TEXT, TEXT[] ), but also checks if there are no conflicts with preexisting patches.
---
--- Third argument is array of names of patches that conflict with current one. So
--- if any of them is installed - register_patch will error out.
---
--- ## \_v.unregister_patch( TEXT )
---
--- Removes information about given patch from the versioning data.
---
--- It doesn't remove objects that were created by this patch - just removes
--- metainformation.
---
--- ## \_v.assert_user_is_superuser()
---
--- Make sure that current patch is being loaded by superuser.
---
--- If it's not - it will raise exception, and break transaction.
---
--- ## \_v.assert_user_is_not_superuser()
---
--- Make sure that current patch is not being loaded by superuser.
---
--- If it is - it will raise exception, and break transaction.
---
--- ## \_v.assert_user_is_one_of(TEXT, TEXT, ... )
---
--- Make sure that current patch is being loaded by one of listed users.
---
--- If ```current_user``` is not listed as one of arguments - function will raise
--- exception and break the transaction.
-
-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.
-CREATE SCHEMA IF NOT EXISTS _v;
-COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.';
-
-CREATE TABLE IF NOT EXISTS _v.patches (
- patch_name TEXT PRIMARY KEY,
- applied_tsz TIMESTAMPTZ NOT NULL DEFAULT now(),
- applied_by TEXT NOT NULL,
- requires TEXT[],
- conflicts TEXT[]
-);
-COMMENT ON TABLE _v.patches IS 'Contains information about what patches are currently applied on database.';
-COMMENT ON COLUMN _v.patches.patch_name IS 'Name of patch, has to be unique for every patch.';
-COMMENT ON COLUMN _v.patches.applied_tsz IS 'When the patch was applied.';
-COMMENT ON COLUMN _v.patches.applied_by IS 'Who applied this patch (PostgreSQL username)';
-COMMENT ON COLUMN _v.patches.requires IS 'List of patches that are required for given patch.';
-COMMENT ON COLUMN _v.patches.conflicts IS 'List of patches that conflict with given patch.';
-
-CREATE OR REPLACE FUNCTION _v.register_patch( IN in_patch_name TEXT, IN in_requirements TEXT[], in_conflicts TEXT[], OUT versioning INT4 ) RETURNS setof INT4 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;
-$$ language plpgsql;
-COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[], TEXT[] ) IS 'Function to register patches in database. Raises exception if there are conflicts, prerequisites are not installed or the migration has already been installed.';
-
-CREATE OR REPLACE FUNCTION _v.register_patch( TEXT, TEXT[] ) RETURNS setof INT4 AS $$
- SELECT _v.register_patch( $1, $2, NULL );
-$$ language sql;
-COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[] ) IS 'Wrapper to allow registration of patches without conflicts.';
-CREATE OR REPLACE FUNCTION _v.register_patch( TEXT ) RETURNS setof INT4 AS $$
- SELECT _v.register_patch( $1, NULL, NULL );
-$$ language sql;
-COMMENT ON FUNCTION _v.register_patch( TEXT ) IS 'Wrapper to allow registration of patches without requirements and conflicts.';
-
-CREATE OR REPLACE FUNCTION _v.unregister_patch( IN in_patch_name TEXT, OUT versioning INT4 ) RETURNS setof INT4 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;
-$$ language plpgsql;
-COMMENT ON FUNCTION _v.unregister_patch( TEXT ) IS 'Function to unregister patches in database. Dies if the patch is not registered, or if unregistering it would break dependencies.';
-
-CREATE OR REPLACE FUNCTION _v.assert_patch_is_applied( IN in_patch_name TEXT ) RETURNS TEXT 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;
-$$ language plpgsql;
-COMMENT ON FUNCTION _v.assert_patch_is_applied( TEXT ) IS 'Function that can be used to make sure that patch has been applied.';
-
-CREATE OR REPLACE FUNCTION _v.assert_user_is_superuser() RETURNS TEXT 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;
-$$ language plpgsql;
-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.';
-
-CREATE OR REPLACE FUNCTION _v.assert_user_is_not_superuser() RETURNS TEXT 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;
-$$ language plpgsql;
-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.';
-
-CREATE OR REPLACE FUNCTION _v.assert_user_is_one_of(VARIADIC p_acceptable_users TEXT[] ) RETURNS TEXT 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;
-$$ language plpgsql;
-COMMENT ON FUNCTION _v.assert_user_is_one_of(TEXT[]) IS 'Function that can be used to make sure that patch is being applied by one of defined users.';
-
-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 cab193dbe..000000000
--- a/src/exchangedb/drop0001.sql
+++ /dev/null
@@ -1,75 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2014--2021 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU General Public License as published by the Free Software
--- Foundation; either version 3, or (at your option) any later version.
---
--- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
--- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
--- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
---
--- You should have received a copy of the GNU General Public License along with
--- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
---
-
--- Everything in one big transaction
-BEGIN;
-
--- This script DROPs all of the tables we create.
---
--- Unlike the other SQL files, it SHOULD be updated to reflect the
--- latest requirements for dropping tables.
-
--- Unregister patch (exchange-0001.sql)
-SELECT _v.unregister_patch('exchange-0001');
-
--- Drops for exchange-0001.sql
-DROP TABLE IF EXISTS revolving_work_shards CASCADE;
-DROP TABLE IF EXISTS extensions CASCADE;
-DROP TABLE IF EXISTS auditors CASCADE;
-DROP TABLE IF EXISTS auditor_denom_sigs CASCADE;
-DROP TABLE IF EXISTS exchange_sign_keys CASCADE;
-DROP TABLE IF EXISTS wire_accounts CASCADE;
-DROP TABLE IF EXISTS signkey_revocations CASCADE;
-DROP TABLE IF EXISTS work_shards CASCADE;
-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_targets CASCADE;
-DROP TABLE IF EXISTS wire_fee CASCADE;
-DROP TABLE IF EXISTS deposits CASCADE;
-DROP TABLE IF EXISTS extension_details 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 FUNCTION IF EXISTS exchange_do_withdraw(bigint,int,bytea,bytea,bytea,bytea,bytea,bigint,bigint) ;
-
-DROP FUNCTION IF EXISTS exchange_do_withdraw_limit_check(bytea,bigint,bigint,int) ;
-
-DROP FUNCTION IF EXISTS exchange_do_deposit;
-
-DROP FUNCTION IF EXISTS exchange_do_melt;
-
-DROP FUNCTION IF EXISTS exchange_do_refund;
-
-DROP FUNCTION IF EXISTS exchange_do_recoup_to_coin;
-
-DROP FUNCTION IF EXISTS exchange_do_recoup_to_reserve;
-
--- FIXME: drop other stored functions!
-
--- And we're out of here...
-COMMIT;
diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql
index 51fd26eca..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--2021 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,1863 +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
- (denominations_serial BIGSERIAL 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_restrictions 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_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 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_restrictions
- 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 IF NOT EXISTS denominations_by_expire_legal_index
- ON denominations
- (expire_legal);
-
-
-CREATE TABLE IF NOT EXISTS denomination_revocations
- (denom_revocations_serial_id BIGSERIAL 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';
-
-
-CREATE TABLE IF NOT EXISTS wire_targets
- (wire_target_serial_id BIGSERIAL -- UNIQUE
- ,h_payto BYTEA PRIMARY KEY CHECK (LENGTH(h_payto)=64)
- ,payto_uri VARCHAR NOT NULL
- ,kyc_ok BOOLEAN NOT NULL DEFAULT (FALSE)
- ,external_id VARCHAR
- )
- PARTITION BY HASH (h_payto);
-COMMENT ON TABLE wire_targets
- IS 'All senders and recipients of money via the exchange';
-COMMENT ON COLUMN wire_targets.payto_uri
- IS 'Can be a regular bank account, or also be a URI identifying a reserve-account (for P2P payments)';
-COMMENT ON COLUMN wire_targets.h_payto
- IS 'Unsalted hash of payto_uri';
-COMMENT ON COLUMN wire_targets.kyc_ok
- IS 'true if the KYC check was passed successfully';
-COMMENT ON COLUMN wire_targets.external_id
- IS 'Name of the user that was used for OAuth 2.0-based legitimization';
-CREATE TABLE IF NOT EXISTS wire_targets_default
- PARTITION OF wire_targets
- FOR VALUES WITH (MODULUS 1, REMAINDER 0);
-
-CREATE TABLE IF NOT EXISTS reserves
- (reserve_uuid BIGSERIAL
- ,reserve_pub BYTEA PRIMARY KEY CHECK(LENGTH(reserve_pub)=32)
- ,current_balance_val INT8 NOT NULL
- ,current_balance_frac INT4 NOT NULL
- ,expiration_date INT8 NOT NULL
- ,gc_date INT8 NOT NULL
- )
- PARTITION BY HASH (reserve_pub);
-COMMENT ON TABLE reserves
- IS 'Summarizes the balance of a reserve. Updated when new funds are added or withdrawn.';
-COMMENT ON COLUMN reserves.reserve_pub
- IS 'EdDSA public key of the reserve. Knowledge of the private key implies ownership over the balance.';
-COMMENT ON COLUMN reserves.current_balance_val
- IS 'Current balance remaining with the reserve';
-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 TABLE IF NOT EXISTS reserves_default
- PARTITION OF reserves
- FOR VALUES WITH (MODULUS 1, REMAINDER 0);
-
-CREATE INDEX IF NOT EXISTS reserves_by_expiration_index
- ON reserves
- (expiration_date
- ,current_balance_val
- ,current_balance_frac
- );
-COMMENT ON INDEX reserves_by_expiration_index
- IS 'used in get_expired_reserves';
-CREATE INDEX IF NOT EXISTS reserves_by_reserve_uuid_index
- ON reserves
- (reserve_uuid);
-CREATE INDEX IF NOT EXISTS reserves_by_gc_date_index
- ON reserves
- (gc_date);
-COMMENT ON INDEX reserves_by_gc_date_index
- IS 'for reserve garbage collection';
-
-
-CREATE TABLE IF NOT EXISTS reserves_in
- (reserve_in_serial_id BIGSERIAL -- UNIQUE
- ,reserve_pub BYTEA PRIMARY KEY REFERENCES reserves (reserve_pub) ON DELETE CASCADE
- ,wire_reference INT8 NOT NULL
- ,credit_val INT8 NOT NULL
- ,credit_frac INT4 NOT NULL
- ,wire_source_serial_id INT8 NOT NULL -- REFERENCES wire_targets (wire_target_serial_id)
- ,exchange_account_section TEXT NOT NULL
- ,execution_date INT8 NOT NULL
- )
- PARTITION BY HASH (reserve_pub);
-COMMENT ON TABLE reserves_in
- IS 'list of transfers of funds into the reserves, one per incoming wire transfer';
-COMMENT ON COLUMN reserves_in.wire_source_serial_id
- IS 'Identifies the debited bank account and KYC status';
-COMMENT ON COLUMN reserves_in.reserve_pub
- IS 'Public key of the reserve. Private key signifies ownership of the remaining balance.';
-COMMENT ON COLUMN reserves_in.credit_val
- IS 'Amount that was transferred into the reserve';
-CREATE TABLE IF NOT EXISTS reserves_in_default
- PARTITION OF reserves_in
- FOR VALUES WITH (MODULUS 1, REMAINDER 0);
-
-CREATE INDEX IF NOT EXISTS reserves_in_by_reserve_in_serial_id_index
- ON reserves_in
- (reserve_in_serial_id);
-CREATE INDEX IF NOT EXISTS reserves_in_by_exchange_account_section_execution_date_index
- ON reserves_in
- (exchange_account_section
- ,execution_date
- );
-CREATE INDEX IF NOT EXISTS reserves_in_by_exchange_account_reserve_in_serial_id_index
- ON reserves_in
- (exchange_account_section,
- reserve_in_serial_id DESC
- );
-
-
-CREATE TABLE IF NOT EXISTS reserves_close
- (close_uuid BIGSERIAL -- UNIQUE / 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)
- ,wire_target_serial_id INT8 NOT NULL -- REFERENCES wire_targets (wire_target_serial_id)
- ,amount_val INT8 NOT NULL
- ,amount_frac INT4 NOT NULL
- ,closing_fee_val INT8 NOT NULL
- ,closing_fee_frac INT4 NOT NULL)
- PARTITION BY HASH (reserve_pub);
-COMMENT ON TABLE reserves_close
- IS 'wire transfers executed by the reserve to close reserves';
-COMMENT ON COLUMN reserves_close.wire_target_serial_id
- IS 'Identifies the credited bank account (and KYC status). Note that closing does not depend on KYC.';
-CREATE TABLE IF NOT EXISTS reserves_close_default
- PARTITION OF reserves_close
- FOR VALUES WITH (MODULUS 1, REMAINDER 0);
-
-CREATE INDEX IF NOT EXISTS reserves_close_by_close_uuid_index
- ON reserves_close
- (close_uuid);
-CREATE INDEX IF NOT EXISTS reserves_close_by_reserve_pub_index
- 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)
- ,denominations_serial INT8 NOT NULL REFERENCES denominations (denominations_serial)
- ,denom_sig BYTEA NOT NULL
- ,reserve_uuid INT8 NOT NULL -- REFERENCES reserves (reserve_uuid) 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
- )
- PARTITION BY HASH (h_blind_ev);
-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.denominations_serial
- IS 'We do not CASCADE ON DELETE here, we may keep the denomination data alive';
-CREATE TABLE IF NOT EXISTS reserves_out_default
- PARTITION OF reserves_out
- FOR VALUES WITH (MODULUS 1, REMAINDER 0);
-
-CREATE INDEX IF NOT EXISTS reserves_out_by_reserve_out_serial_id_index
- ON reserves_out
- (reserve_out_serial_id);
-CREATE INDEX IF NOT EXISTS reserves_out_by_reserve_uuid_and_execution_date_index
- ON reserves_out
- (reserve_uuid, execution_date);
-COMMENT ON INDEX reserves_out_by_reserve_uuid_and_execution_date_index
- IS 'for get_reserves_out and exchange_do_withdraw_limit_check';
-
-
-CREATE TABLE IF NOT EXISTS auditors
- (auditor_uuid BIGSERIAL UNIQUE
- ,auditor_pub BYTEA PRIMARY KEY CHECK (LENGTH(auditor_pub)=32)
- ,auditor_name VARCHAR NOT NULL
- ,auditor_url VARCHAR 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.';
-
-
-CREATE TABLE IF NOT EXISTS auditor_denom_sigs
- (auditor_denom_serial BIGSERIAL 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.';
-
-
-CREATE TABLE IF NOT EXISTS exchange_sign_keys
- (esk_serial BIGSERIAL 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.';
-
-
-CREATE TABLE IF NOT EXISTS signkey_revocations
- (signkey_revocations_serial_id BIGSERIAL 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';
-
-
-CREATE TABLE IF NOT EXISTS extensions
- (extension_id BIGSERIAL UNIQUE
- ,name VARCHAR NOT NULL UNIQUE
- ,config BYTEA NOT NULL
- ,config_sig BYTEA NOT NULL
- );
-COMMENT ON TABLE extensions
- IS 'Configurations of the activated extensions';
-COMMENT ON COLUMN extensions.name
- IS 'Name of the extension';
-COMMENT ON COLUMN extensions.config
- IS 'Configuration of the extension as JSON-blob';
-COMMENT ON COLUMN extensions.config
- IS 'Signature of the configuration of an extension, signed with the master key of the exchange';
-
-
-CREATE TABLE IF NOT EXISTS known_coins
- (known_coin_id BIGSERIAL -- UNIQUE
- ,denominations_serial INT8 NOT NULL REFERENCES denominations (denominations_serial) ON DELETE CASCADE
- ,coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (LENGTH(coin_pub)=32)
- ,age_hash BYTEA CHECK (LENGTH(age_hash)=32)
- ,denom_sig BYTEA NOT NULL
- ,remaining_val INT8 NOT NULL
- ,remaining_frac INT4 NOT NULL
- )
- PARTITION BY HASH (coin_pub); -- FIXME: or include denominations_serial? or multi-level partitioning?
-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';
-COMMENT ON COLUMN known_coins.denominations_serial
- IS 'Denomination of the coin, determines the value of the original coin and applicable fees for coin-specific operations.';
-COMMENT ON COLUMN known_coins.coin_pub
- IS 'EdDSA public key of the coin';
-COMMENT ON COLUMN known_coins.remaining_val
- IS 'Value of the coin that remains to be spent';
-COMMENT ON COLUMN known_coins.age_hash
- IS 'Optional hash for age restrictions as per DD 24 (active if denom_type has the respective bit set)';
-COMMENT ON COLUMN known_coins.denom_sig
- IS '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.';
-CREATE TABLE IF NOT EXISTS known_coins_default
- PARTITION OF known_coins
- FOR VALUES WITH (MODULUS 1, REMAINDER 0);
-
-CREATE INDEX IF NOT EXISTS known_coins_by_known_coin_id_index
- ON known_coins
- (known_coin_id);
-
-
-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
- )
- PARTITION BY HASH (rc);
-COMMENT ON TABLE refresh_commitments
- IS 'Commitments made when melting coins and the gamma value chosen by the exchange.';
-COMMENT ON COLUMN refresh_commitments.noreveal_index
- IS 'The gamma value chosen by the exchange in the cut-and-choose protocol';
-COMMENT ON COLUMN refresh_commitments.rc
- IS 'Commitment made by the client, hash over the various client inputs in the cut-and-choose protocol';
-COMMENT ON COLUMN refresh_commitments.old_coin_pub
- IS 'Coin being melted in the refresh process.';
-CREATE TABLE IF NOT EXISTS refresh_commitments_default
- PARTITION OF refresh_commitments
- FOR VALUES WITH (MODULUS 1, REMAINDER 0);
-
-CREATE INDEX IF NOT EXISTS refresh_commitments_by_melt_serial_id_index
- ON refresh_commitments
- (melt_serial_id);
-CREATE INDEX IF NOT EXISTS refresh_commitments_by_old_coin_pub_index
- ON refresh_commitments
- (old_coin_pub);
-
-
-CREATE TABLE IF NOT EXISTS refresh_revealed_coins
- (rrc_serial BIGSERIAL -- UNIQUE
- ,melt_serial_id INT8 NOT NULL -- REFERENCES refresh_commitments (melt_serial_id) ON DELETE CASCADE
- ,freshcoin_index INT4 NOT NULL
- ,link_sig BYTEA NOT NULL CHECK(LENGTH(link_sig)=64)
- ,denominations_serial INT8 NOT NULL REFERENCES denominations (denominations_serial) ON DELETE CASCADE
- ,coin_ev BYTEA NOT NULL -- UNIQUE
- ,h_coin_ev BYTEA NOT NULL CHECK(LENGTH(h_coin_ev)=64) -- UNIQUE
- ,ev_sig BYTEA NOT NULL
- -- ,PRIMARY KEY (melt_serial_id, freshcoin_index) -- done per shard
- )
- PARTITION BY HASH (melt_serial_id);
-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.rrc_serial
- IS 'needed for exchange-auditor replication logic';
-COMMENT ON COLUMN refresh_revealed_coins.melt_serial_id
- IS 'Identifies the refresh commitment (rc) of 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 TABLE IF NOT EXISTS refresh_revealed_coins_default
- PARTITION OF refresh_revealed_coins
- FOR VALUES WITH (MODULUS 1, REMAINDER 0);
--- We do require this primary key on each shard!
-ALTER TABLE refresh_revealed_coins_default
- ADD PRIMARY KEY (melt_serial_id, freshcoin_index);
-
-CREATE INDEX IF NOT EXISTS refresh_revealed_coins_by_rrc_serial_index
- ON refresh_revealed_coins
- (rrc_serial);
-CREATE INDEX IF NOT EXISTS refresh_revealed_coins_by_melt_serial_id_index
- ON refresh_revealed_coins
- (melt_serial_id);
-
-
-CREATE TABLE IF NOT EXISTS refresh_transfer_keys
- (rtc_serial BIGSERIAL -- UNIQUE
- ,melt_serial_id INT8 PRIMARY KEY -- REFERENCES refresh_commitments (melt_serial_id) ON DELETE CASCADE
- ,transfer_pub BYTEA NOT NULL CHECK(LENGTH(transfer_pub)=32)
- ,transfer_privs BYTEA NOT NULL
- )
- PARTITION BY HASH (melt_serial_id);
-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.rtc_serial
- IS 'needed for exchange-auditor replication logic';
-COMMENT ON COLUMN refresh_transfer_keys.melt_serial_id
- IS 'Identifies the refresh commitment (rc) of the 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 TABLE IF NOT EXISTS refresh_transfer_keys_default
- PARTITION OF refresh_transfer_keys
- FOR VALUES WITH (MODULUS 1, REMAINDER 0);
-
-CREATE INDEX IF NOT EXISTS refresh_transfer_keys_by_rtc_serial_index
- ON refresh_transfer_keys
- (rtc_serial);
-
-
-CREATE TABLE IF NOT EXISTS extension_details
- (extension_details_serial_id BIGSERIAL PRIMARY KEY
- ,extension_options VARCHAR);
-COMMENT ON TABLE extension_details
- IS 'Extensions that were provided with deposits (not yet used).';
-COMMENT ON COLUMN extension_details.extension_options
- IS 'JSON object with options set that the exchange needs to consider when executing a deposit. Supported details depend on the extensions supported by the exchange.';
-
-
-CREATE TABLE IF NOT EXISTS deposits
- (deposit_serial_id BIGSERIAL -- PRIMARY KEY
- ,shard INT8 NOT NULL
- ,known_coin_id INT8 NOT NULL -- REFERENCES known_coins (known_coin_id) ON DELETE CASCADE
- ,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_serial_id INT8 NOT NULL -- REFERENCES wire_targets (wire_target_serial_id)
- ,tiny BOOLEAN NOT NULL DEFAULT FALSE
- ,done BOOLEAN NOT NULL DEFAULT FALSE
- ,extension_blocked BOOLEAN NOT NULL DEFAULT FALSE
- ,extension_details_serial_id INT8 REFERENCES extension_details (extension_details_serial_id) ON DELETE CASCADE
- ,UNIQUE (shard, known_coin_id, merchant_pub, h_contract_terms)
- )
- PARTITION BY HASH (shard);
-CREATE TABLE IF NOT EXISTS deposits_default
- PARTITION OF deposits
- FOR VALUES WITH (MODULUS 1, REMAINDER 0);
-
-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.shard
- IS 'Used for load sharding. Should be set based on h_payto and merchant_pub. 64-bit value because we need an *unsigned* 32-bit value.';
-COMMENT ON COLUMN deposits.wire_target_serial_id
- IS 'Identifies the target bank account and KYC status';
-COMMENT ON COLUMN deposits.wire_salt
- IS 'Salt used when hashing the payto://-URI to get the h_wire';
-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.extension_blocked
- IS 'True if the aggregation of the deposit is currently blocked by some extension mechanism. Used to filter out deposits that must not be processed by the canonical deposit logic.';
-COMMENT ON COLUMN deposits.extension_details_serial_id
- IS 'References extensions table, NULL if extensions are not used';
-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)';
-
--- FIXME: check if we can ALWAYS include the shard in the WHERE clauses,
--- thereby resulting in a much better use of the index: we could do (shard,deposit_serial_id)!
-CREATE INDEX IF NOT EXISTS deposits_deposit_by_serial_id_index
- ON deposits
- (deposit_serial_id);
-CREATE INDEX IF NOT EXISTS deposits_for_get_ready_index
- ON deposits
- (shard ASC
- ,done
- ,extension_blocked
- ,tiny
- ,wire_deadline ASC
- );
-COMMENT ON INDEX deposits_for_get_ready_index
- IS 'for deposits_get_ready';
--- FIXME: check if we can ALWAYS include the shard in the WHERE clauses,
--- thereby resulting in a much better use of the index: we could do (shard,merchant_pub, ...)!
-CREATE INDEX IF NOT EXISTS deposits_for_iterate_matching_index
- ON deposits
- (merchant_pub
- ,wire_target_serial_id
- ,done
- ,extension_blocked
- ,refund_deadline ASC
- );
-COMMENT ON INDEX deposits_for_iterate_matching_index
- IS 'for deposits_iterate_matching';
-
-
-CREATE TABLE IF NOT EXISTS refunds
- (refund_serial_id BIGSERIAL -- UNIQUE
- ,deposit_serial_id INT8 NOT NULL -- REFERENCES deposits (deposit_serial_id) ON DELETE CASCADE
- ,merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64)
- ,rtransaction_id INT8 NOT NULL
- ,amount_with_fee_val INT8 NOT NULL
- ,amount_with_fee_frac INT4 NOT NULL
- -- ,PRIMARY KEY (deposit_serial_id, rtransaction_id) -- done per shard!
- )
- PARTITION BY HASH (deposit_serial_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.deposit_serial_id
- IS 'Identifies ONLY the merchant_pub, h_contract_terms and known_coin_id. Multiple deposits may match a refund, this only identifies one of them.';
-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 TABLE IF NOT EXISTS refunds_default
- PARTITION OF refunds
- FOR VALUES WITH (MODULUS 1, REMAINDER 0);
-ALTER TABLE refunds_default
- ADD PRIMARY KEY (deposit_serial_id, rtransaction_id);
-
-CREATE INDEX IF NOT EXISTS refunds_by_refund_serial_id_index
- ON refunds
- (refund_serial_id);
-
-
-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_serial_id INT8 NOT NULL -- REFERENCES wire_targets (wire_target_serial_id)
- ,exchange_account_section TEXT NOT NULL
- ,amount_val INT8 NOT NULL
- ,amount_frac INT4 NOT NULL
- )
- PARTITION BY HASH (wtid_raw);
-COMMENT ON TABLE wire_out
- IS 'wire transfers the exchange has executed';
-COMMENT ON COLUMN wire_out.exchange_account_section
- IS 'identifies the configuration section with the debit account of this payment';
-COMMENT ON COLUMN wire_out.wire_target_serial_id
- IS 'Identifies the credited bank account and KYC status';
-CREATE TABLE IF NOT EXISTS wire_out_default
- PARTITION OF wire_out
- FOR VALUES WITH (MODULUS 1, REMAINDER 0);
-
-CREATE INDEX IF NOT EXISTS wire_out_by_wireout_uuid_index
- ON wire_out
- (wireout_uuid);
-CREATE INDEX IF NOT EXISTS wire_out_by_wire_target_serial_id_index
- ON wire_out
- (wire_target_serial_id);
-
-
-
-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
- )
- PARTITION BY HASH (deposit_serial_id);
-COMMENT ON TABLE aggregation_tracking
- IS 'mapping from wire transfer identifiers (WTID) to deposits (and back)';
-COMMENT ON COLUMN aggregation_tracking.wtid_raw
- IS 'We first create entries in the aggregation_tracking table and then finally the wire_out entry once we know the total amount. Hence the constraint must be deferrable and we cannot use a wireout_uuid here, because we do not have it when these rows are created. Changing the logic to first INSERT a dummy row into wire_out and then UPDATEing that row in the same transaction would theoretically reduce per-deposit storage costs by 5 percent (24/~460 bytes).';
-CREATE TABLE IF NOT EXISTS aggregation_tracking_default
- PARTITION OF aggregation_tracking
- FOR VALUES WITH (MODULUS 1, REMAINDER 0);
-
-CREATE INDEX IF NOT EXISTS aggregation_tracking_by_aggregation_serial_id_index
- ON aggregation_tracking
- (aggregation_serial_id);
-CREATE INDEX IF NOT EXISTS aggregation_tracking_by_wtid_raw_index
- ON aggregation_tracking
- (wtid_raw);
-COMMENT ON INDEX aggregation_tracking_by_wtid_raw_index
- IS 'for lookup_transactions';
-
-
-CREATE TABLE IF NOT EXISTS wire_fee
- (wire_fee_serial BIGSERIAL UNIQUE
- ,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';
-COMMENT ON COLUMN wire_fee.wire_fee_serial
- IS 'needed for exchange-auditor replication logic';
-
-CREATE INDEX IF NOT EXISTS wire_fee_by_end_date_index
- ON wire_fee
- (end_date);
-
-
-CREATE TABLE IF NOT EXISTS recoup
- (recoup_uuid BIGSERIAL -- UNIQUE
- ,known_coin_id INT8 NOT NULL -- REFERENCES known_coins (known_coin_id)
- ,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
- ,recoup_timestamp INT8 NOT NULL
- ,reserve_out_serial_id INT8 NOT NULL -- REFERENCES reserves_out (reserve_out_serial_id) ON DELETE CASCADE
- )
- PARTITION BY RANGE (known_coin_id);
-COMMENT ON TABLE recoup
- IS '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.';
-COMMENT ON COLUMN recoup.known_coin_id
- IS 'Coin that is being debited in the recoup. Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!';
-COMMENT ON COLUMN recoup.reserve_out_serial_id
- IS 'Identifies the h_blind_ev of the recouped coin and provides the link to the credited reserve.';
-COMMENT ON COLUMN recoup.coin_sig
- IS 'Signature by the coin affirming the recoup, of type TALER_SIGNATURE_WALLET_COIN_RECOUP';
-COMMENT ON COLUMN recoup.coin_blind
- IS '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.';
-CREATE TABLE IF NOT EXISTS recoup_default
- PARTITION OF recoup
- DEFAULT;
-
-CREATE INDEX IF NOT EXISTS recoup_by_recoup_uuid_index
- ON recoup
- (recoup_uuid);
-CREATE INDEX IF NOT EXISTS recoup_by_reserve_out_serial_id_index
- ON recoup
- (reserve_out_serial_id);
-CREATE INDEX IF NOT EXISTS recoup_by_known_coin_id_index
- ON recoup
- (known_coin_id);
-
-
-CREATE TABLE IF NOT EXISTS recoup_refresh
- (recoup_refresh_uuid BIGSERIAL -- UNIQUE
- ,known_coin_id INT8 NOT NULL -- REFERENCES known_coins (known_coin_id)
- ,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
- ,recoup_timestamp INT8 NOT NULL
- ,rrc_serial INT8 NOT NULL -- REFERENCES refresh_revealed_coins (rrc_serial) ON DELETE CASCADE -- UNIQUE
- )
- PARTITION BY RANGE (known_coin_id);
-COMMENT ON TABLE recoup_refresh
- IS '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.';
-COMMENT ON COLUMN recoup_refresh.known_coin_id
- IS 'Refreshed coin of a revoked denomination where the residual value is credited to the old coin. Do not CASCADE ON DROP on the known_coin_id, as we may keep the coin alive!';
-COMMENT ON COLUMN recoup_refresh.rrc_serial
- IS 'Link to the refresh operation. Also identifies the h_blind_ev of the recouped coin (as h_coin_ev).';
-COMMENT ON COLUMN recoup_refresh.coin_blind
- IS '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.';
-CREATE TABLE IF NOT EXISTS recoup_refresh_default
- PARTITION OF recoup_refresh
- DEFAULT;
-
-CREATE INDEX IF NOT EXISTS recoup_refresh_by_recoup_refresh_uuid_index
- ON recoup_refresh
- (recoup_refresh_uuid);
-CREATE INDEX IF NOT EXISTS recoup_refresh_by_rrc_serial_index
- ON recoup_refresh
- (rrc_serial);
-CREATE INDEX IF NOT EXISTS recoup_refresh_by_known_coin_id_index
- ON recoup_refresh
- (known_coin_id);
-
-
-CREATE TABLE IF NOT EXISTS prewire
- (prewire_uuid BIGSERIAL PRIMARY KEY
- ,wire_method TEXT NOT NULL
- ,finished BOOLEAN NOT NULL DEFAULT false
- ,failed BOOLEAN NOT NULL DEFAULT false
- ,buf BYTEA NOT NULL
- )
- PARTITION BY RANGE (prewire_uuid);
-COMMENT ON TABLE prewire
- IS 'pre-commit data for wire transfers we are about to execute';
-COMMENT ON COLUMN prewire.failed
- IS 'set to TRUE if the bank responded with a non-transient failure to our transfer request';
-COMMENT ON COLUMN prewire.finished
- IS 'set to TRUE once bank confirmed receiving the wire transfer request';
-COMMENT ON COLUMN prewire.buf
- IS 'serialized data to send to the bank to execute the wire transfer';
-CREATE TABLE IF NOT EXISTS prewire_default
- PARTITION OF prewire
- DEFAULT;
-
-CREATE INDEX IF NOT EXISTS prewire_by_finished_index
- ON prewire
- (finished);
-COMMENT ON INDEX prewire_by_finished_index
- IS 'for gc_prewire';
--- FIXME: find a way to combine these two indices?
-CREATE INDEX IF NOT EXISTS prewire_by_failed_finished_index
- ON prewire
- (failed,finished);
-COMMENT ON INDEX prewire_by_failed_finished_index
- IS 'for wire_prepare_data_get';
-
-
-CREATE TABLE IF NOT EXISTS wire_accounts
- (payto_uri VARCHAR PRIMARY KEY
- ,master_sig BYTEA CHECK (LENGTH(master_sig)=64)
- ,is_active BOOLEAN NOT NULL
- ,last_change INT8 NOT 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.';
--- "wire_accounts" has no BIGSERIAL because it is a 'mutable' table
--- and is of no concern to the auditor
-
-
-CREATE TABLE IF NOT EXISTS work_shards
- (shard_serial_id BIGSERIAL UNIQUE
- ,last_attempt INT8 NOT NULL
- ,start_row INT8 NOT NULL
- ,end_row INT8 NOT NULL
- ,completed BOOLEAN NOT NULL DEFAULT FALSE
- ,job_name VARCHAR 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 IF NOT EXISTS work_shards_by_job_name_completed_last_attempt_index
- ON work_shards
- (job_name
- ,completed
- ,last_attempt
- );
-
-
-CREATE UNLOGGED TABLE IF NOT EXISTS revolving_work_shards
- (shard_serial_id BIGSERIAL UNIQUE
- ,last_attempt INT8 NOT NULL
- ,start_row INT4 NOT NULL
- ,end_row INT4 NOT NULL
- ,active BOOLEAN NOT NULL DEFAULT FALSE
- ,job_name VARCHAR 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 IF NOT EXISTS revolving_work_shards_by_job_name_active_last_attempt_index
- ON revolving_work_shards
- (job_name
- ,active
- ,last_attempt
- );
-
-
--- Stored procedures
-
-
-CREATE OR REPLACE FUNCTION exchange_do_withdraw(
- IN amount_val INT8,
- IN amount_frac INT4,
- IN h_denom_pub BYTEA,
- IN rpub BYTEA,
- IN reserve_sig BYTEA,
- IN h_coin_envelope BYTEA,
- IN denom_sig BYTEA,
- IN now INT8,
- IN min_reserve_gc INT8,
- OUT reserve_found BOOLEAN,
- OUT balance_ok BOOLEAN,
- OUT kycok BOOLEAN,
- OUT account_uuid INT8,
- OUT ruuid INT8)
+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 $$
-DECLARE
- reserve_gc INT8;
-DECLARE
- denom_serial INT8;
-DECLARE
- reserve_val INT8;
-DECLARE
- reserve_frac INT4;
BEGIN
--- Shards: reserves by reserve_pub (SELECT)
--- reserves_out (INSERT, with CONFLICT detection) by h_blind_ev
--- reserves by reserve_pub (UPDATE)
--- reserves_in by reserve_pub (SELECT)
--- wire_targets by wire_target_serial_id
-
-SELECT denominations_serial INTO denom_serial
- FROM denominations
- WHERE denom_pub_hash=h_denom_pub;
-
-IF NOT FOUND
-THEN
- -- denomination unknown, should be impossible!
- reserve_found=FALSE;
- balance_ok=FALSE;
- kycok=FALSE;
- account_uuid=0;
- ruuid=0;
- ASSERT false, 'denomination unknown';
- RETURN;
-END IF;
-
-SELECT
- current_balance_val
- ,current_balance_frac
- ,gc_date
- ,reserve_uuid
- INTO
- reserve_val
- ,reserve_frac
- ,reserve_gc
- ,ruuid
- FROM reserves
- WHERE reserves.reserve_pub=rpub;
-
-IF NOT FOUND
-THEN
- -- reserve unknown
- reserve_found=FALSE;
- balance_ok=FALSE;
- kycok=FALSE;
- account_uuid=0;
- ruuid=0;
- RETURN;
-END IF;
-
--- We optimistically insert, and then on conflict declare
--- the query successful due to idempotency.
-INSERT INTO reserves_out
- (h_blind_ev
- ,denominations_serial
- ,denom_sig
- ,reserve_uuid
- ,reserve_sig
- ,execution_date
- ,amount_with_fee_val
- ,amount_with_fee_frac)
-VALUES
- (h_coin_envelope
- ,denom_serial
- ,denom_sig
- ,ruuid
- ,reserve_sig
- ,now
- ,amount_val
- ,amount_frac)
-ON CONFLICT DO NOTHING;
-
-IF NOT FOUND
-THEN
- -- idempotent query, all constraints must be satisfied
- reserve_found=TRUE;
- balance_ok=TRUE;
- kycok=TRUE;
- account_uuid=0;
- RETURN;
-END IF;
-
--- Check reserve balance is sufficient.
-IF (reserve_val > amount_val)
-THEN
- IF (reserve_frac >= amount_frac)
+ IF (partition_suffix IS NULL)
THEN
- reserve_val=reserve_val - amount_val;
- reserve_frac=reserve_frac - amount_frac;
+ -- no partitioning, disable option
+ main_table_partition_str = '';
ELSE
- reserve_val=reserve_val - amount_val - 1;
- reserve_frac=reserve_frac + 100000000 - amount_frac;
+ IF (partition_suffix::int > 0)
+ THEN
+ -- sharding, add shard name
+ table_name=table_name || '_' || partition_suffix;
+ END IF;
END IF;
-ELSE
- IF (reserve_val = amount_val) AND (reserve_frac >= amount_frac)
- THEN
- reserve_val=0;
- reserve_frac=reserve_frac - amount_frac;
- ELSE
- reserve_found=TRUE;
- balance_ok=FALSE;
- kycok=FALSE; -- we do not really know or care
- account_uuid=0;
- RETURN;
- END IF;
-END IF;
-
--- Calculate new expiration dates.
-min_reserve_gc=GREATEST(min_reserve_gc,reserve_gc);
-
--- Update reserve balance.
-UPDATE reserves SET
- gc_date=min_reserve_gc
- ,current_balance_val=reserve_val
- ,current_balance_frac=reserve_frac
-WHERE
- reserves.reserve_pub=rpub;
-
-reserve_found=TRUE;
-balance_ok=TRUE;
-
--- Obtain KYC status based on the last wire transfer into
--- this reserve. FIXME: likely not adequate for reserves that got P2P transfers!
-SELECT
- kyc_ok
- ,wire_source_serial_id
- INTO
- kycok
- ,account_uuid
- FROM reserves_in
- JOIN wire_targets ON (wire_source_serial_id = wire_target_serial_id)
- WHERE reserve_pub=rpub
- LIMIT 1; -- limit 1 should not be required (without p2p transfers)
-
-END $$;
-
-COMMENT ON FUNCTION exchange_do_withdraw(INT8, INT4, BYTEA, BYTEA, BYTEA, BYTEA, BYTEA, INT8, INT8)
- IS 'Checks whether the reserve has sufficient balance for a withdraw operation (or the request is repeated and was previously approved) and if so updates the database with the result';
-
-
-
-CREATE OR REPLACE FUNCTION exchange_do_withdraw_limit_check(
- IN ruuid INT8,
- IN start_time INT8,
- IN upper_limit_val INT8,
- IN upper_limit_frac INT4,
- OUT below_limit BOOLEAN)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- total_val INT8;
-DECLARE
- total_frac INT8; -- INT4 could overflow during accumulation!
-BEGIN
--- NOTE: Read-only, but crosses shards.
--- Shards: reserves by reserve_pub
--- reserves_out by reserve_uuid -- crosses shards!!
-
-
-SELECT
- SUM(amount_with_fee_val) -- overflow here is not plausible
- ,SUM(CAST(amount_with_fee_frac AS INT8)) -- compute using 64 bits
- INTO
- total_val
- ,total_frac
- FROM reserves_out
- WHERE reserve_uuid=ruuid
- AND execution_date > start_time;
-
--- normalize result
-total_val = total_val + total_frac / 100000000;
-total_frac = total_frac % 100000000;
-
--- compare to threshold
-below_limit = (total_val < upper_limit_val) OR
- ( (total_val = upper_limit_val) AND
- (total_frac <= upper_limit_frac) );
-END $$;
-
-COMMENT ON FUNCTION exchange_do_withdraw_limit_check(INT8, INT8, INT8, INT4)
- IS 'Check whether the withdrawals from the given reserve since the given time are below the given threshold';
-
-
-
-
-CREATE OR REPLACE FUNCTION exchange_do_deposit(
- IN in_amount_with_fee_val INT8,
- IN in_amount_with_fee_frac INT4,
- IN in_h_contract_terms BYTEA,
- IN in_wire_salt BYTEA,
- IN in_wallet_timestamp INT8,
- IN in_exchange_timestamp INT8,
- IN in_refund_deadline INT8,
- IN in_wire_deadline INT8,
- IN in_merchant_pub BYTEA,
- IN in_receiver_wire_account VARCHAR,
- IN in_h_payto BYTEA,
- IN in_known_coin_id INT8,
- IN in_coin_pub BYTEA,
- IN in_coin_sig BYTEA,
- IN in_shard INT8,
- IN in_extension_blocked BOOLEAN,
- IN in_extension_details VARCHAR,
- OUT out_exchange_timestamp INT8,
- OUT out_balance_ok BOOLEAN,
- OUT out_conflict BOOLEAN)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- wtsi INT8; -- wire target serial id
-DECLARE
- xdi INT8; -- eXstension details serial id
-BEGIN
--- Shards: INSERT extension_details (by extension_details_serial_id)
--- INSERT wire_targets (by h_payto), on CONFLICT DO NOTHING;
--- INSERT deposits (by shard + known_coin_id, merchant_pub, h_contract_terms), ON CONFLICT DO NOTHING;
--- UPDATE known_coins (by coin_pub)
-
-IF NOT NULL in_extension_details
-THEN
- INSERT INTO extension_details
- (extension_options)
- VALUES
- (in_extension_details)
- RETURNING extension_details_serial_id INTO xdi;
-ELSE
- xdi=NULL;
-END IF;
-
-
-INSERT INTO wire_targets
- (h_payto
- ,payto_uri)
- VALUES
- (in_h_payto
- ,in_receiver_wire_account)
-ON CONFLICT (h_payto) DO NOTHING
- RETURNING wire_target_serial_id INTO wtsi;
-
-IF NOT FOUND
-THEN
- SELECT wire_target_serial_id
- INTO wtsi
- FROM wire_targets
- WHERE h_payto=in_h_payto;
-END IF;
-
-
-INSERT INTO deposits
- (shard
- ,known_coin_id
- ,amount_with_fee_val
- ,amount_with_fee_frac
- ,wallet_timestamp
- ,exchange_timestamp
- ,refund_deadline
- ,wire_deadline
- ,merchant_pub
- ,h_contract_terms
- ,coin_sig
- ,wire_salt
- ,wire_target_serial_id
- ,extension_blocked
- ,extension_details_serial_id
- )
- VALUES
- (in_shard
- ,in_known_coin_id
- ,in_amount_with_fee_val
- ,in_amount_with_fee_frac
- ,in_wallet_timestamp
- ,in_exchange_timestamp
- ,in_refund_deadline
- ,in_wire_deadline
- ,in_merchant_pub
- ,in_h_contract_terms
- ,in_coin_sig
- ,in_wire_salt
- ,wtsi
- ,in_extension_blocked
- ,xdi)
- 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.
- SELECT
- exchange_timestamp
- INTO
- out_exchange_timestamp
- FROM deposits
- WHERE
- shard=in_shard AND
- known_coin_id=in_known_coin_id AND
- merchant_pub=in_merchant_pub AND
- h_contract_terms=in_h_contract_terms AND
- coin_sig=in_coin_sig;
-
- IF NOT FOUND
- THEN
- -- Deposit exists, but with differences. Not allowed.
- out_balance_ok=FALSE;
- out_conflict=TRUE;
- RETURN;
- END IF;
-
- -- Idempotent request known, return success.
- out_balance_ok=TRUE;
- out_conflict=FALSE;
-
- RETURN;
-END IF;
-
-
-out_exchange_timestamp=in_exchange_timestamp;
-
--- Check and update balance of the coin.
-UPDATE known_coins
- SET
- remaining_frac=remaining_frac-in_amount_with_fee_frac
- + CASE
- WHEN remaining_frac < in_amount_with_fee_frac
- THEN 100000000
- ELSE 0
- END,
- remaining_val=remaining_val-in_amount_with_fee_val
- - CASE
- WHEN remaining_frac < in_amount_with_fee_frac
- THEN 1
- ELSE 0
- END
- WHERE coin_pub=in_coin_pub
- AND ( (remaining_val > in_amount_with_fee_val) OR
- ( (remaining_frac >= in_amount_with_fee_frac) AND
- (remaining_val >= in_amount_with_fee_val) ) );
-
-IF NOT FOUND
-THEN
- -- Insufficient balance.
- out_balance_ok=FALSE;
- out_conflict=FALSE;
- RETURN;
-END IF;
-
--- Everything fine, return success!
-out_balance_ok=TRUE;
-out_conflict=FALSE;
-
+ EXECUTE FORMAT(
+ table_definition,
+ table_name,
+ main_table_partition_str
+ );
END $$;
+COMMENT ON FUNCTION create_partitioned_table
+ IS 'Generic function to create a table that is partitioned or sharded.';
-CREATE OR REPLACE FUNCTION exchange_do_melt(
- IN in_amount_with_fee_val INT8,
- IN in_amount_with_fee_frac INT4,
- 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)
+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
--- 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 refresh_commitments
- (rc
- ,old_coin_pub
- ,old_coin_sig
- ,amount_with_fee_val
- ,amount_with_fee_frac
- ,noreveal_index
- )
- VALUES
- (in_rc
- ,in_old_coin_pub
- ,in_old_coin_sig
- ,in_amount_with_fee_val
- ,in_amount_with_fee_frac
- ,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 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
+ IF ( (partition_suffix IS NOT NULL) AND
+ (partition_suffix::int > 0) )
THEN
- out_zombie_bad=TRUE;
- out_balance_ok=FALSE;
- RETURN;
+ -- sharding, add shard name
+ table_name=table_name || '_' || partition_suffix;
END IF;
-END IF;
-
-out_zombie_bad=FALSE; -- zombie is OK
-
-
--- Check and update balance of the coin.
-UPDATE known_coins
- SET
- remaining_frac=remaining_frac-in_amount_with_fee_frac
- + CASE
- WHEN remaining_frac < in_amount_with_fee_frac
- THEN 100000000
- ELSE 0
- END,
- remaining_val=remaining_val-in_amount_with_fee_val
- - CASE
- WHEN remaining_frac < in_amount_with_fee_frac
- THEN 1
- ELSE 0
- END
- WHERE coin_pub=in_old_coin_pub
- AND ( (remaining_val > in_amount_with_fee_val) OR
- ( (remaining_frac >= in_amount_with_fee_frac) AND
- (remaining_val >= in_amount_with_fee_val) ) );
-
-IF NOT FOUND
-THEN
- -- Insufficient balance.
- out_noreveal_index=-1;
- out_balance_ok=FALSE;
- RETURN;
-END IF;
-
--- Everything fine, return success!
-out_balance_ok=TRUE;
-out_noreveal_index=in_noreveal_index;
-
+ EXECUTE FORMAT(
+ 'COMMENT ON TABLE %s IS %s'
+ ,table_name
+ ,quote_literal(table_comment)
+ );
END $$;
+COMMENT ON FUNCTION comment_partitioned_table
+ IS 'Generic function to create a comment on table that is partitioned.';
-CREATE OR REPLACE FUNCTION exchange_do_refund(
- IN in_amount_with_fee_val INT8,
- IN in_amount_with_fee_frac INT4,
- IN in_amount_val INT8,
- IN in_amount_frac INT4,
- IN in_deposit_fee_val INT8,
- IN in_deposit_fee_frac INT4,
- 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)
+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 $$
-DECLARE
- dsi INT8; -- ID of deposit being refunded
-DECLARE
- tmp_val INT8; -- total amount refunded
-DECLARE
- tmp_frac INT8; -- total amount refunded
-DECLARE
- deposit_val INT8; -- amount that was originally deposited
-DECLARE
- deposit_frac INT8; -- amount that was originally deposited
BEGIN
--- Shards: SELECT deposits (by shard, known_coin_id,h_contract_terms, merchant_pub)
--- INSERT refunds (by deposit_serial_id, rtransaction_id) ON CONFLICT DO NOTHING
--- SELECT refunds (by deposit_serial_id)
--- UPDATE known_coins (by coin_pub)
-
-SELECT
- deposit_serial_id
- ,amount_with_fee_val
- ,amount_with_fee_frac
- ,done
-INTO
- dsi
- ,deposit_val
- ,deposit_frac
- ,out_gone
-FROM deposits
-WHERE shard=in_deposit_shard
- AND known_coin_id=in_known_coin_id
- AND h_contract_terms=in_h_contract_terms
- AND merchant_pub=in_merchant_pub;
-
-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
- (deposit_serial_id
- ,merchant_sig
- ,rtransaction_id
- ,amount_with_fee_val
- ,amount_with_fee_frac
- )
- VALUES
- (dsi
- ,in_merchant_sig
- ,in_rtransaction_id
- ,in_amount_with_fee_val
- ,in_amount_with_fee_frac)
- 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 refunds
- WHERE
- deposit_serial_id=dsi AND
- rtransaction_id=in_rtransaction_id AND
- amount_with_fee_val=in_amount_with_fee_val AND
- amount_with_fee_frac=in_amount_with_fee_frac;
-
- 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(amount_with_fee_val) -- overflow here is not plausible
- ,SUM(CAST(amount_with_fee_frac AS INT8)) -- compute using 64 bits
- INTO
- tmp_val
- ,tmp_frac
- FROM refunds
- WHERE
- deposit_serial_id=dsi;
-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)
+ IF ( (partition_suffix IS NOT NULL) AND
+ (partition_suffix::int > 0) )
THEN
- out_refund_ok=TRUE;
- ELSE
- out_refund_ok=FALSE;
+ -- sharding, add shard name
+ table_name=table_name || '_' || partition_suffix;
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
- SET
- remaining_frac=remaining_frac+in_amount_frac
- - CASE
- WHEN remaining_frac+in_amount_frac >= 100000000
- THEN 100000000
- ELSE 0
- END,
- remaining_val=remaining_val+in_amount_val
- + CASE
- WHEN 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(INT8, INT4, BYTEA, BOOLEAN, BOOLEAN)
--- IS 'Executes a refund operation, checking that the corresponding deposit was sufficient to cover the refunded amount';
-
-
-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_val INT8; -- amount recouped
-DECLARE
- tmp_frac INT8; -- amount recouped
-BEGIN
--- Shards: SELECT known_coins (by coin_pub)
--- SELECT recoup (by known_coin_id)
--- UPDATE known_coins (by coin_pub)
--- UPDATE reserves (by reserve_pub)
--- INSERT recoup (by known_coin_id)
-
-out_internal_failure=FALSE;
-
-
--- Check remaining balance of the coin.
-SELECT
- remaining_frac
- ,remaining_val
- INTO
- tmp_frac
- ,tmp_val
-FROM known_coins
- WHERE coin_pub=in_coin_pub;
-
-IF NOT FOUND
-THEN
- out_internal_failure=TRUE;
- out_recoup_ok=FALSE;
- RETURN;
-END IF;
-
-IF tmp_val + tmp_frac = 0
-THEN
- -- Check for idempotency
- SELECT
- recoup_timestamp
- INTO
- out_recoup_timestamp
- FROM recoup
- WHERE known_coin_id=in_known_coin_id;
-
- out_recoup_ok=FOUND;
- RETURN;
-END IF;
-
-
--- Update balance of the coin.
-UPDATE known_coins
- SET
- remaining_frac=0
- ,remaining_val=0
- WHERE coin_pub=in_coin_pub;
-
-
--- Credit the reserve and update reserve timers.
-UPDATE reserves
- SET
- current_balance_frac=current_balance_frac+tmp_frac
- - CASE
- WHEN current_balance_frac+tmp_frac >= 100000000
- THEN 100000000
- ELSE 0
- END,
- current_balance_val=current_balance_val+tmp_val
- + CASE
- WHEN current_balance_frac+tmp_frac >= 100000000
- THEN 1
- ELSE 0
- END,
- 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 recoup
- (known_coin_id
- ,coin_sig
- ,coin_blind
- ,amount_val
- ,amount_frac
- ,recoup_timestamp
- ,reserve_out_serial_id
- )
-VALUES
- (in_known_coin_id
- ,in_coin_sig
- ,in_coin_blind
- ,tmp_val
- ,tmp_frac
- ,in_recoup_timestamp
- ,in_reserve_out_serial_id);
-
--- Normal end, everything is fine.
-out_recoup_ok=TRUE;
-out_recoup_timestamp=in_recoup_timestamp;
-
+ EXECUTE FORMAT(
+ 'COMMENT ON COLUMN %s.%s IS %s'
+ ,table_name
+ ,column_name
+ ,quote_literal(table_comment)
+ );
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';
-
-
-
+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 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
+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
- tmp_val INT8; -- amount recouped
-DECLARE
- tmp_frac INT8; -- amount recouped
+ 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
-
--- Shards: UPDATE known_coins (by coin_pub)
--- SELECT recoup_refresh (by known_coin_id)
--- UPDATE known_coins (by coin_pub)
--- INSERT recoup_refresh (by known_coin_id)
-
-
-out_internal_failure=FALSE;
-
-
--- Check remaining balance of the coin.
-SELECT
- remaining_frac
- ,remaining_val
- INTO
- tmp_frac
- ,tmp_val
-FROM known_coins
- WHERE coin_pub=in_coin_pub;
-
-IF NOT FOUND
-THEN
- out_internal_failure=TRUE;
- out_recoup_ok=FALSE;
- RETURN;
-END IF;
-
-IF tmp_val + tmp_frac = 0
-THEN
- -- Check for idempotency
- SELECT
- recoup_timestamp
- INTO
- out_recoup_timestamp
- FROM recoup_refresh
- WHERE known_coin_id=in_known_coin_id;
- out_recoup_ok=FOUND;
- RETURN;
-END IF;
-
--- Update balance of the coin.
-UPDATE known_coins
- SET
- remaining_frac=0
- ,remaining_val=0
- WHERE coin_pub=in_coin_pub;
-
-
--- Credit the old coin.
-UPDATE known_coins
- SET
- remaining_frac=remaining_frac+tmp_frac
- - CASE
- WHEN remaining_frac+tmp_frac >= 100000000
- THEN 100000000
- ELSE 0
- END,
- remaining_val=remaining_val+tmp_val
- + CASE
- WHEN 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
- (known_coin_id
- ,coin_sig
- ,coin_blind
- ,amount_val
- ,amount_frac
- ,recoup_timestamp
- ,rrc_serial
- )
-VALUES
- (in_known_coin_id
- ,in_coin_sig
- ,in_coin_blind
- ,tmp_val
- ,tmp_frac
- ,in_recoup_timestamp
- ,in_rrc_serial);
-
--- Normal end, everything is fine.
-out_recoup_ok=TRUE;
-out_recoup_timestamp=in_recoup_timestamp;
-
+ 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 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';
-
-
-
-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
-DECLARE
- melt_min INT8; -- minimum melt still alive
-DECLARE
- coin_min INT8; -- minimum known_coin still alive
-DECLARE
- deposit_min INT8; -- minimum deposit still alive
-DECLARE
- reserve_out_min INT8; -- minimum reserve_out still alive
-BEGIN
-
-DELETE FROM prewire
- WHERE finished=TRUE;
-
-DELETE FROM wire_fee
- WHERE end_date < in_ancient_date;
-
--- TODO: use closing fee as threshold?
-DELETE FROM reserves
- WHERE gc_date < in_now
- AND current_balance_val = 0
- AND current_balance_frac = 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;
-
-
-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 known_coin_id IN
- (SELECT DISTINCT known_coin_id
- FROM recoup))
- AND denominations_serial NOT IN
- (SELECT DISTINCT denominations_serial
- FROM known_coins
- WHERE known_coin_id IN
- (SELECT DISTINCT known_coin_id
- 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 deposits
- WHERE known_coin_id < coin_min;
-
-SELECT
- deposit_serial_id
- INTO
- deposit_min
- FROM deposits
- ORDER BY deposit_serial_id ASC
- LIMIT 1;
-
-DELETE FROM refunds
- WHERE deposit_serial_id < deposit_min;
-
-DELETE FROM aggregation_tracking
- WHERE deposit_serial_id < deposit_min;
-
-
-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 77748bf5a..2bfcb2ca0 100644
--- a/src/exchangedb/exchangedb.conf
+++ b/src/exchangedb/exchangedb.conf
@@ -26,3 +26,11 @@ 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
+
+# What is the desired delay between a transaction being ready and the
+# aggregator triggering on it?
+AGGREGATOR_SHIFT = 1 s
+
+# 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 e41074822..e668134e1 100644
--- a/src/exchangedb/exchangedb_accounts.c
+++ b/src/exchangedb/exchangedb_accounts.c
@@ -199,7 +199,6 @@ add_account_cb (void *cls,
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
section,
"PAYTO_URI");
- lc->res = GNUNET_SYSERR;
return;
}
method = TALER_payto_get_method (payto_uri);
diff --git a/src/exchangedb/exchangedb_transactions.c b/src/exchangedb/exchangedb_transactions.c
index aaf28a9ea..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,
@@ -129,6 +119,49 @@ 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)
diff --git a/src/exchangedb/irbt_callbacks.c b/src/exchangedb/irbt_callbacks.c
deleted file mode 100644
index 0e0264e89..000000000
--- a/src/exchangedb/irbt_callbacks.c
+++ /dev/null
@@ -1,691 +0,0 @@
-/*
- This file is part of GNUnet
- Copyright (C) 2020, 2021 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/irbt_callbacks.c
- * @brief callbacks used by postgres_insert_records_by_table, to be
- * inlined into the plugin
- * @author Christian Grothoff
- */
-
-
-/**
- * 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_DenominationHash denom_hash;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_auto_from_type (&denom_hash),
- 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 (&td->details.denominations.coin),
- TALER_PQ_query_param_amount (
- &td->details.denominations.fee_withdraw),
- TALER_PQ_query_param_amount (
- &td->details.denominations.fee_deposit),
- TALER_PQ_query_param_amount (
- &td->details.denominations.fee_refresh),
- TALER_PQ_query_param_amount (
- &td->details.denominations.fee_refund),
- GNUNET_PQ_query_param_end
- };
-
- 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
- };
-
- 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_PaytoHash payto_hash;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&payto_hash),
- GNUNET_PQ_query_param_string (
- td->details.wire_targets.payto_uri),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.wire_targets.kyc_ok),
- GNUNET_PQ_query_param_string (
- td->details.wire_targets.external_id),
- GNUNET_PQ_query_param_end
- };
-
- 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 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),
- TALER_PQ_query_param_amount (&td->details.reserves.current_balance),
- 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
- };
-
- 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 (&td->details.reserves_in.credit),
- GNUNET_PQ_query_param_uint64 (&td->details.reserves_in.sender_account),
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_reserves_in",
- 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_uint64 (
- &td->details.reserves_close.wire_target_serial_id),
- TALER_PQ_query_param_amount (&td->details.reserves_close.amount),
- TALER_PQ_query_param_amount (&td->details.reserves_close.closing_fee),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.reserves_close.reserve_pub),
- GNUNET_PQ_query_param_end
- };
-
- 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 (
- &td->details.reserves_out.amount_with_fee),
- GNUNET_PQ_query_param_end
- };
-
- 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)
-{
- uint8_t is_active = td->details.auditors.is_active ? 1 : 0;
- 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_auto_from_type (&is_active),
- GNUNET_PQ_query_param_timestamp (&td->details.auditors.last_change),
- GNUNET_PQ_query_param_end
- };
-
- 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
- };
-
- 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
- };
-
- 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
- };
-
- 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
- };
-
- 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 (
- &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
- };
-
- 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),
- 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
- };
-
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_refresh_transfer_keys",
- 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_deposits (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- uint8_t tiny = td->details.deposits.tiny ? 1 : 0;
- uint8_t done = td->details.deposits.done ? 1 : 0;
- uint8_t extension_blocked = td->details.deposits.extension_blocked ? 1 : 0;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_uint64 (&td->details.deposits.shard),
- GNUNET_PQ_query_param_uint64 (&td->details.deposits.known_coin_id),
- TALER_PQ_query_param_amount (&td->details.deposits.amount_with_fee),
- GNUNET_PQ_query_param_timestamp (&td->details.deposits.wallet_timestamp),
- GNUNET_PQ_query_param_timestamp (
- &td->details.deposits.exchange_timestamp),
- GNUNET_PQ_query_param_timestamp (&td->details.deposits.refund_deadline),
- GNUNET_PQ_query_param_timestamp (&td->details.deposits.wire_deadline),
- GNUNET_PQ_query_param_auto_from_type (&td->details.deposits.merchant_pub),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.deposits.h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (&td->details.deposits.coin_sig),
- GNUNET_PQ_query_param_auto_from_type (&td->details.deposits.wire_salt),
- GNUNET_PQ_query_param_uint64 (&td->details.deposits.wire_target_serial_id),
- GNUNET_PQ_query_param_auto_from_type (&tiny),
- GNUNET_PQ_query_param_auto_from_type (&done),
- GNUNET_PQ_query_param_auto_from_type (&extension_blocked),
- GNUNET_PQ_query_param_uint64 (
- &td->details.deposits.extension_details_serial_id),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_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.merchant_sig),
- GNUNET_PQ_query_param_uint64 (&td->details.refunds.rtransaction_id),
- TALER_PQ_query_param_amount (&td->details.refunds.amount_with_fee),
- GNUNET_PQ_query_param_uint64 (&td->details.refunds.deposit_serial_id),
- GNUNET_PQ_query_param_end
- };
-
- 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_uint64 (&td->details.wire_out.wire_target_serial_id),
- GNUNET_PQ_query_param_string (
- td->details.wire_out.exchange_account_section),
- TALER_PQ_query_param_amount (&td->details.wire_out.amount),
- GNUNET_PQ_query_param_end
- };
-
- 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.deposit_serial_id),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.aggregation_tracking.wtid_raw),
- GNUNET_PQ_query_param_end
- };
-
- 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 (&td->details.wire_fee.wire_fee),
- TALER_PQ_query_param_amount (&td->details.wire_fee.closing_fee),
- GNUNET_PQ_query_param_auto_from_type (&td->details.wire_fee.master_sig),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_wire_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 (&td->details.recoup.amount),
- GNUNET_PQ_query_param_timestamp (&td->details.recoup.timestamp),
- GNUNET_PQ_query_param_uint64 (&td->details.recoup.known_coin_id),
- GNUNET_PQ_query_param_uint64 (&td->details.recoup.reserve_out_serial_id),
- GNUNET_PQ_query_param_end
- };
-
- 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 (&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_uint64 (&td->details.recoup_refresh.rrc_serial),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_recoup_refresh",
- params);
-}
-
-
-/* end of irbt_callbacks.c */
diff --git a/src/exchangedb/lrbt_callbacks.c b/src/exchangedb/lrbt_callbacks.c
deleted file mode 100644
index 04be98696..000000000
--- a/src/exchangedb/lrbt_callbacks.c
+++ /dev/null
@@ -1,1302 +0,0 @@
-/*
- This file is part of GNUnet
- Copyright (C) 2020, 2021 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/lrbt_callbacks.c
- * @brief callbacks used by postgres_lookup_records_by_table, to be
- * inlined into the plugin
- * @author Christian Grothoff
- */
-
-
-/**
- * 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_restrictions",
- &td.details.denominations.age_restrictions),
- 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.fee_withdraw),
- TALER_PQ_RESULT_SPEC_AMOUNT (
- "fee_deposit",
- &td.details.denominations.fee_deposit),
- TALER_PQ_RESULT_SPEC_AMOUNT (
- "fee_refresh",
- &td.details.denominations.fee_refresh),
- TALER_PQ_RESULT_SPEC_AMOUNT (
- "fee_refund",
- &td.details.denominations.fee_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_auto_from_type ("kyc_ok",
- &td.details.wire_targets.kyc_ok),
- GNUNET_PQ_result_spec_string ("external_id",
- &td.details.wire_targets.external_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 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 PostgresClosure *pg = ctx->pg;
- 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),
- TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance",
- &td.details.reserves.current_balance),
- 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_uint64 (
- "wire_source_serial_id",
- &td.details.reserves_in.sender_account),
- 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_uint64 (
- "wire_target_serial_id",
- &td.details.reserves_close.wire_target_serial_id),
- 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_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++)
- {
- uint8_t is_active8 = 0;
- 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_auto_from_type ("is_active",
- &is_active8),
- 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;
- }
- td.details.auditors.is_active = (0 != is_active8);
- 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),
- 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;
- }
- 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 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_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_DEPOSITS
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint8_t tiny = 0; /* initialized to make compiler happy */
- uint8_t done = 0; /* initialized to make compiler happy */
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 (
- "serial",
- &td.serial),
- GNUNET_PQ_result_spec_uint64 (
- "shard",
- &td.details.deposits.shard),
- GNUNET_PQ_result_spec_uint64 (
- "known_coin_id",
- &td.details.deposits.known_coin_id),
- TALER_PQ_RESULT_SPEC_AMOUNT (
- "amount_with_fee",
- &td.details.deposits.amount_with_fee),
- GNUNET_PQ_result_spec_timestamp (
- "wallet_timestamp",
- &td.details.deposits.wallet_timestamp),
- GNUNET_PQ_result_spec_timestamp (
- "exchange_timestamp",
- &td.details.deposits.exchange_timestamp),
- GNUNET_PQ_result_spec_timestamp (
- "refund_deadline",
- &td.details.deposits.refund_deadline),
- GNUNET_PQ_result_spec_timestamp (
- "wire_deadline",
- &td.details.deposits.wire_deadline),
- GNUNET_PQ_result_spec_auto_from_type (
- "merchant_pub",
- &td.details.deposits.merchant_pub),
- GNUNET_PQ_result_spec_auto_from_type (
- "h_contract_terms",
- &td.details.deposits.h_contract_terms),
- GNUNET_PQ_result_spec_auto_from_type (
- "coin_sig",
- &td.details.deposits.coin_sig),
- GNUNET_PQ_result_spec_auto_from_type (
- "wire_salt",
- &td.details.deposits.wire_salt),
- GNUNET_PQ_result_spec_uint64 (
- "wire_target_serial_id",
- &td.details.deposits.wire_target_serial_id),
- GNUNET_PQ_result_spec_auto_from_type (
- "tiny",
- &td.details.deposits.tiny),
- GNUNET_PQ_result_spec_auto_from_type (
- "done",
- &td.details.deposits.done),
- GNUNET_PQ_result_spec_auto_from_type (
- "extension_blocked",
- &td.details.deposits.extension_blocked),
- GNUNET_PQ_result_spec_uint64 (
- "extension_details_serial_id",
- &td.details.deposits.extension_details_serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- td.details.deposits.tiny = (0 != tiny);
- td.details.deposits.done = (0 != done);
- 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 (
- "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 (
- "deposit_serial_id",
- &td.details.refunds.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_uint64 (
- "wire_target_serial_id",
- &td.details.wire_out.wire_target_serial_id),
- 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 (
- "deposit_serial_id",
- &td.details.aggregation_tracking.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.wire_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
- &td.details.wire_fee.closing_fee),
- 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 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_uint64 ("known_coin_id",
- &td.details.recoup.known_coin_id),
- 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_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);
- }
-}
-
-
-/* end of lrbt_callbacks.c */
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/util/extensions.c b/src/exchangedb/pg_drop_tables.h
index 87dd16b4d..58729d5ec 100644
--- a/src/util/extensions.c
+++ b/src/exchangedb/pg_drop_tables.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 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 General Public License as published by the Free Software
@@ -14,36 +14,25 @@
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
+ * @file exchangedb/pg_drop_tables.h
+ * @brief implementation of the drop_tables function for Postgres
+ * @author Christian Grothoff
*/
-#include "platform.h"
-#include "taler_util.h"
-#include "taler_extensions.h"
-#include "stdint.h"
-
-enum GNUNET_GenericReturnValue
-TALER_extension_get_by_name (const char *name,
- const struct TALER_Extension **extensions,
- const struct TALER_Extension **ext)
-{
-
- const struct TALER_Extension *it = *extensions;
+#ifndef PG_DROP_TABLES_H
+#define PG_DROP_TABLES_H
- for (; NULL != it; it++)
- {
- if (0 == strncmp (name,
- it->name,
- strlen (it->name)))
- {
- *ext = it;
- return GNUNET_OK;
- }
- }
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
- return GNUNET_NO;
-}
+/**
+ * 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);
-/* end of extensions.c */
+#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 a07ae78c1..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)
@@ -71,6 +69,38 @@ common_free_reserve_history (void *cls,
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;
@@ -83,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)
@@ -134,6 +159,31 @@ common_free_coin_transaction_list (void *cls,
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 a0bc14d52..108b55219 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014--2021 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,54 +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 <poll.h>
-#include <pthread.h>
-#include <sys/eventfd.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.
@@ -89,2695 +248,13 @@
/**
- * 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;
-
- /**
- * 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;
-
- /**
- * Did we initialize the prepared statements
- * for this session?
- */
- bool init;
-
-};
-
-
-/**
- * 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 enum GNUNET_GenericReturnValue
-postgres_drop_tables (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_Context *conn;
-
- conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
- "exchangedb-postgres",
- "drop",
- NULL,
- NULL);
- if (NULL == conn)
- return GNUNET_SYSERR;
- GNUNET_PQ_disconnect (conn);
- if (NULL != pg->conn)
- {
- GNUNET_PQ_disconnect (pg->conn);
- pg->conn = NULL;
- pg->init = false;
- }
- 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 enum GNUNET_GenericReturnValue
-postgres_create_tables (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_Context *conn;
-
- conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
- "exchangedb-postgres",
- "exchange-",
- NULL,
- NULL);
- if (NULL == conn)
- return GNUNET_SYSERR;
- GNUNET_PQ_disconnect (conn);
- return GNUNET_OK;
-}
-
-
-/**
- * Initialize prepared statements for @a pg.
- *
- * @param[in,out] pg connection to initialize
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-prepare_statements (struct PostgresClosure *pg)
-{
- enum GNUNET_GenericReturnValue ret;
- struct GNUNET_PQ_PreparedStatement ps[] = {
- /* Used in #postgres_insert_denomination_info() and
- #postgres_add_denomination_key() */
- GNUNET_PQ_make_prepare (
- "denomination_insert",
- "INSERT INTO denominations "
- "(denom_pub_hash"
- ",denom_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);",
- 17),
- /* Used in #postgres_iterate_denomination_info() */
- GNUNET_PQ_make_prepare (
- "denomination_iterate",
- "SELECT"
- " 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_iterate_denominations() */
- GNUNET_PQ_make_prepare (
- "select_denominations",
- "SELECT"
- " denominations.master_sig"
- ",denom_revocations_serial_id IS NOT NULL AS revoked"
- ",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"
- " LEFT JOIN "
- " denomination_revocations USING (denominations_serial);",
- 0),
- /* Used in #postgres_iterate_active_signkeys() */
- GNUNET_PQ_make_prepare (
- "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);",
- 1),
- /* Used in #postgres_iterate_auditor_denominations() */
- GNUNET_PQ_make_prepare (
- "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;",
- 0),
- /* Used in #postgres_iterate_active_auditors() */
- GNUNET_PQ_make_prepare (
- "select_auditors",
- "SELECT"
- " auditor_pub"
- ",auditor_url"
- ",auditor_name"
- " FROM auditors"
- " WHERE"
- " is_active;",
- 0),
- /* Used in #postgres_get_denomination_info() */
- GNUNET_PQ_make_prepare (
- "denomination_get",
- "SELECT"
- " 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 "
- "(denominations_serial"
- ",master_sig"
- ") SELECT denominations_serial,$2"
- " FROM denominations"
- " WHERE denom_pub_hash=$1;",
- 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 denominations_serial="
- " (SELECT denominations_serial"
- " FROM denominations"
- " WHERE denom_pub_hash=$1);",
- 1),
- /* Used in #postgres_reserves_get() */
- GNUNET_PQ_make_prepare (
- "reserves_get_with_kyc",
- "SELECT"
- " current_balance_val"
- ",current_balance_frac"
- ",expiration_date"
- ",gc_date"
- ",kyc_ok"
- ",wire_target_serial_id AS payment_target_uuid"
- " FROM reserves"
- " JOIN reserves_in ri USING (reserve_pub)"
- " JOIN wire_targets wt "
- " ON (ri.wire_source_serial_id = wt.wire_target_serial_id)"
- " WHERE reserve_pub=$1"
- " LIMIT 1;",
- 1),
- /* Used in #postgres_set_kyc_ok() */
- GNUNET_PQ_make_prepare (
- "set_kyc_ok",
- "UPDATE wire_targets"
- " SET kyc_ok=TRUE"
- ",external_id=$2"
- " WHERE wire_target_serial_id=$1",
- 2),
- GNUNET_PQ_make_prepare (
- "get_kyc_h_payto",
- "SELECT"
- " h_payto"
- " FROM wire_targets"
- " WHERE wire_target_serial_id=$1"
- " LIMIT 1;",
- 1),
- /* Used in #postgres_select_kyc_status() */
- GNUNET_PQ_make_prepare (
- "select_kyc_status",
- "SELECT"
- " kyc_ok"
- ",h_payto"
- " FROM wire_targets"
- " WHERE"
- " wire_target_serial_id=$1",
- 1),
- /* Used in #postgres_inselect_wallet_kyc_status() */
- GNUNET_PQ_make_prepare (
- "insert_kyc_status",
- "INSERT INTO wire_targets"
- " (h_payto"
- " ,payto_uri"
- " ) VALUES "
- " ($1, $2)"
- " RETURNING wire_target_serial_id",
- 2),
- GNUNET_PQ_make_prepare (
- "select_kyc_status_by_payto",
- "SELECT "
- " kyc_ok"
- ",wire_target_serial_id"
- " FROM wire_targets"
- " WHERE h_payto=$1;",
- 1),
- /* Used in #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;",
- 1),
- GNUNET_PQ_make_prepare (
- "reserve_create",
- "INSERT INTO reserves "
- "(reserve_pub"
- ",current_balance_val"
- ",current_balance_frac"
- ",expiration_date"
- ",gc_date"
- ") VALUES "
- "($1, $2, $3, $4, $5)"
- " ON CONFLICT DO NOTHING"
- " RETURNING reserve_uuid;",
- 5),
- /* Used in #postgres_insert_reserve_closed() */
- GNUNET_PQ_make_prepare (
- "reserves_close_insert",
- "INSERT INTO reserves_close "
- "(reserve_pub"
- ",execution_date"
- ",wtid"
- ",wire_target_serial_id"
- ",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"
- ",wire_source_serial_id"
- ",execution_date"
- ") VALUES ($1, $2, $3, $4, $5, $6, $7)"
- " ON CONFLICT DO NOTHING;",
- 7),
- /* Used in #postgres_reserves_in_insert() to store transaction details */
- GNUNET_PQ_make_prepare (
- "reserves_in_add_by_pub",
- "INSERT INTO reserves_in "
- "(reserve_pub"
- ",wire_reference"
- ",credit_val"
- ",credit_frac"
- ",exchange_account_section"
- ",wire_source_serial_id"
- ",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 (
- "audit_reserves_in_get_transactions_incr",
- "SELECT"
- " reserves.reserve_pub"
- ",wire_reference"
- ",credit_val"
- ",credit_frac"
- ",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_serial_id = wire_target_serial_id)"
- " 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"
- " reserves.reserve_pub"
- ",wire_reference"
- ",credit_val"
- ",credit_frac"
- ",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_serial_id = wire_target_serial_id)"
- " 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"
- ",payto_uri AS sender_account_details"
- " FROM reserves_in"
- " JOIN wire_targets"
- " ON (wire_source_serial_id = wire_target_serial_id)"
- " WHERE reserve_pub=$1;",
- 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_do_withdraw() 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 (
- "call_withdraw",
- "SELECT "
- " reserve_found"
- ",balance_ok"
- ",kycok AS kyc_ok"
- ",account_uuid AS payment_target_uuid"
- ",ruuid"
- " FROM exchange_do_withdraw"
- " ($1,$2,$3,$4,$5,$6,$7,$8,$9);",
- 9),
- /* Used in #postgres_do_withdraw_limit_check() to check
- if the withdrawals remain below the limit under which
- KYC is not required. */
- GNUNET_PQ_make_prepare (
- "call_withdraw_limit_check",
- "SELECT "
- " below_limit"
- " FROM exchange_do_withdraw_limit_check"
- " ($1,$2,$3,$4);",
- 4),
- /* Used in #postgres_do_deposit() to execute a deposit,
- checking the coin's balance in the process as needed. */
- GNUNET_PQ_make_prepare (
- "call_deposit",
- "SELECT "
- " out_exchange_timestamp AS exchange_timestamp"
- ",out_balance_ok AS balance_ok"
- ",out_conflict AS conflicted"
- " FROM exchange_do_deposit"
- " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17);",
- 17),
- /* Used in #postgres_do_melt() to melt a coin. */
- GNUNET_PQ_make_prepare (
- "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);",
- 8),
- /* Used in #postgres_do_refund() to refund a deposit. */
- GNUNET_PQ_make_prepare (
- "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,$11,$12,$13);",
- 13),
- /* Used in #postgres_do_recoup() to recoup a coin to a reserve. */
- GNUNET_PQ_make_prepare (
- "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);",
- 9),
- /* Used in #postgres_do_recoup_refresh() to recoup a coin to a zombie coin. */
- GNUNET_PQ_make_prepare (
- "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);",
- 7),
- /* 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.denom_pub_hash"
- ",denom_sig"
- ",reserve_sig"
- ",reserves.reserve_pub"
- ",execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_withdraw_val"
- ",denom.fee_withdraw_frac"
- " FROM reserves_out"
- " JOIN reserves"
- " USING (reserve_uuid)"
- " JOIN denominations denom"
- " USING (denominations_serial)"
- " WHERE h_blind_ev=$1;",
- 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.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"
- " JOIN reserves_out"
- " USING (reserve_uuid)"
- " JOIN denominations denom"
- " USING (denominations_serial)"
- " WHERE reserve_pub=$1;",
- 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"
- ",reserves.reserve_pub"
- ",execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",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;",
- 1),
-
- /* Used in #postgres_count_known_coins() */
- GNUNET_PQ_make_prepare (
- "count_known_coins",
- "SELECT"
- " COUNT(*) AS count"
- " FROM known_coins"
- " WHERE denominations_serial="
- " (SELECT denominations_serial"
- " FROM denominations"
- " 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"
- " denominations.denom_pub_hash"
- ",age_hash"
- ",denom_sig"
- " FROM known_coins"
- " JOIN denominations USING (denominations_serial)"
- " WHERE coin_pub=$1;",
- 1),
- /* Used in #postgres_ensure_coin_known() */
- GNUNET_PQ_make_prepare (
- "get_known_coin_dh",
- "SELECT"
- " denominations.denom_pub_hash"
- " FROM known_coins"
- " JOIN denominations USING (denominations_serial)"
- " WHERE coin_pub=$1;",
- 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"
- " denominations.denom_pub_hash"
- ",known_coin_id"
- " FROM known_coins"
- " JOIN denominations USING (denominations_serial)"
- " WHERE coin_pub=$1"
- " FOR SHARE;",
- 1),
- /* Used in #postgres_insert_known_coin() to store the denomination public
- key and signature for a coin known to the exchange.
-
- See also:
- https://stackoverflow.com/questions/34708509/how-to-use-returning-with-on-conflict-in-postgresql/37543015#37543015
- */
- GNUNET_PQ_make_prepare (
- "insert_known_coin",
- "WITH dd"
- " (denominations_serial"
- " ,coin_val"
- " ,coin_frac"
- " ) AS ("
- " SELECT "
- " denominations_serial"
- " ,coin_val"
- " ,coin_frac"
- " 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_hash"
- " ,denom_sig"
- " ,remaining_val"
- " ,remaining_frac"
- " ) SELECT "
- " $1"
- " ,denominations_serial"
- " ,$3"
- " ,$4"
- " ,coin_val"
- " ,coin_frac"
- " FROM dd"
- " ON CONFLICT (coin_pub) DO NOTHING"
- " RETURNING "
- " known_coin_id"
- " ) "
- "SELECT "
- " FALSE AS existed"
- " ,known_coin_id"
- " ,NULL AS denom_pub_hash"
- " ,NULL AS age_hash"
- " FROM ins "
- "UNION ALL "
- "SELECT "
- " TRUE AS existed"
- " ,known_coin_id"
- " ,denom_pub_hash"
- " ,kc.age_hash"
- " FROM input_rows"
- " JOIN known_coins kc USING (coin_pub)"
- " JOIN denominations USING (denominations_serial)"
- " LIMIT 1",
- 4),
-
- /* Used in #postgres_get_melt() to fetch
- high-level information about a melt operation */
- GNUNET_PQ_make_prepare (
- "get_melt",
- "SELECT"
- " denoms.denom_pub_hash"
- ",denoms.fee_refresh_val"
- ",denoms.fee_refresh_frac"
- ",old_coin_pub"
- ",old_coin_sig"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",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;",
- 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"
- ",kc.coin_pub AS 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.denominations_serial = denom.denominations_serial)"
- " 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"
- ",denoms.denom_pub_hash"
- ",denoms.fee_refresh_val"
- ",denoms.fee_refresh_frac"
- ",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;",
- 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 "
- "(melt_serial_id "
- ",freshcoin_index "
- ",link_sig "
- ",denominations_serial "
- ",coin_ev"
- ",h_coin_ev"
- ",ev_sig"
- ") SELECT $1, $2, $3, "
- " denominations_serial, $5, $6, $7"
- " FROM denominations"
- " WHERE denom_pub_hash=$4;",
- 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 "
- " rrc.freshcoin_index"
- ",denom.denom_pub_hash"
- ",rrc.h_coin_ev"
- ",rrc.link_sig"
- ",rrc.coin_ev"
- ",rrc.ev_sig"
- " FROM refresh_commitments"
- " JOIN refresh_revealed_coins rrc"
- " USING (melt_serial_id)"
- " JOIN denominations denom "
- " USING (denominations_serial)"
- " WHERE rc=$1;",
- 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 "
- "(melt_serial_id"
- ",transfer_pub"
- ",transfer_privs"
- ") VALUES ($1, $2, $3);",
- 3),
- /* Used in #postgres_insert_refund() to store refund information */
- GNUNET_PQ_make_prepare (
- "insert_refund",
- "INSERT INTO refunds "
- "(deposit_serial_id "
- ",merchant_sig "
- ",rtransaction_id "
- ",amount_with_fee_val "
- ",amount_with_fee_frac "
- ") SELECT deposit_serial_id, $3, $5, $6, $7"
- " FROM deposits"
- " WHERE known_coin_id="
- " (SELECT known_coin_id "
- " FROM known_coins"
- " WHERE coin_pub=$1)"
- " AND h_contract_terms=$4"
- " AND merchant_pub=$2",
- 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"
- ",refunds.amount_with_fee_val"
- ",refunds.amount_with_fee_frac"
- ",denom.fee_refund_val "
- ",denom.fee_refund_frac "
- ",refund_serial_id"
- " FROM refunds"
- " JOIN deposits USING (deposit_serial_id)"
- " JOIN known_coins USING (known_coin_id)"
- " JOIN denominations denom USING (denominations_serial)"
- " 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"
- " refunds.amount_with_fee_val"
- ",refunds.amount_with_fee_frac"
- " FROM refunds"
- " JOIN deposits USING (deposit_serial_id)"
- " JOIN known_coins USING (known_coin_id)"
- " 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"
- ",kc.coin_pub"
- ",refunds.amount_with_fee_val"
- ",refunds.amount_with_fee_frac"
- ",refund_serial_id"
- " FROM refunds"
- " JOIN deposits USING (deposit_serial_id)"
- " JOIN known_coins kc USING (known_coin_id)"
- " JOIN denominations denom ON (kc.denominations_serial = denom.denominations_serial)"
- " 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 "
- "(known_coin_id"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",wallet_timestamp"
- ",refund_deadline"
- ",wire_deadline"
- ",merchant_pub"
- ",h_contract_terms"
- ",wire_salt"
- ",wire_target_serial_id"
- ",coin_sig"
- ",exchange_timestamp"
- ",shard"
- ") SELECT known_coin_id, $2, $3, $4, $5, $6, "
- " $7, $8, $9, $10, $11, $12, $13"
- " FROM known_coins"
- " WHERE coin_pub=$1"
- " ON CONFLICT DO NOTHING;",
- 13),
- /* 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"
- ",denominations.fee_deposit_val"
- ",denominations.fee_deposit_frac"
- ",wallet_timestamp"
- ",exchange_timestamp"
- ",refund_deadline"
- ",wire_deadline"
- ",h_contract_terms"
- ",wire_salt"
- ",payto_uri AS receiver_wire_account"
- " FROM deposits"
- " JOIN known_coins USING (known_coin_id)"
- " JOIN denominations USING (denominations_serial)"
- " JOIN wire_targets USING (wire_target_serial_id)"
- " WHERE ((coin_pub=$1)"
- " AND (merchant_pub=$3)"
- " AND (h_contract_terms=$2));",
- 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"
- ",wallet_timestamp"
- ",exchange_timestamp"
- ",merchant_pub"
- ",denom.denom_pub"
- ",kc.coin_pub"
- ",coin_sig"
- ",refund_deadline"
- ",wire_deadline"
- ",h_contract_terms"
- ",wire_salt"
- ",payto_uri AS receiver_wire_account"
- ",done"
- ",deposit_serial_id"
- " FROM deposits"
- " JOIN wire_targets USING (wire_target_serial_id)"
- " JOIN known_coins kc USING (known_coin_id)"
- " JOIN denominations denom USING (denominations_serial)"
- " 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_without_wtid",
- "SELECT"
- " kyc_ok"
- ",wire_target_serial_id AS payment_target_uuid"
- ",wire_salt"
- ",payto_uri"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_deposit_val"
- ",denom.fee_deposit_frac"
- ",wire_deadline"
- " FROM deposits"
- " JOIN wire_targets USING (wire_target_serial_id)"
- " JOIN known_coins USING (known_coin_id)"
- " JOIN denominations denom USING (denominations_serial)"
- " WHERE ((coin_pub=$1)"
- " AND (merchant_pub=$3)"
- " AND (h_contract_terms=$2)"
- " );",
- 3),
- /* 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"
- ",h_contract_terms"
- ",payto_uri"
- ",wire_target_serial_id"
- ",merchant_pub"
- ",kc.coin_pub"
- " FROM deposits"
- " JOIN wire_targets "
- " USING (wire_target_serial_id)"
- " JOIN known_coins kc"
- " USING (known_coin_id)"
- " JOIN denominations denom"
- " USING (denominations_serial)"
- " WHERE "
- " shard >= $2"
- " AND shard <= $3"
- " AND done=FALSE"
- " AND extension_blocked=FALSE"
- " AND tiny=FALSE"
- " AND wire_deadline<=$1"
- " AND (kyc_ok OR $4)"
- " ORDER BY "
- " shard ASC"
- " ,wire_deadline ASC"
- " LIMIT 1;",
- 4),
- /* 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"
- ",h_contract_terms"
- ",kc.coin_pub"
- " FROM deposits"
- " JOIN known_coins kc USING (known_coin_id)"
- " JOIN denominations denom USING (denominations_serial)"
- " WHERE"
- " merchant_pub=$1"
- " AND wire_target_serial_id=$2"
- " AND done=FALSE"
- " AND extension_blocked=FALSE"
- " AND refund_deadline<$3"
- " ORDER BY refund_deadline ASC"
- " LIMIT "
- TALER_QUOTE (
- TALER_EXCHANGEDB_MATCHING_DEPOSITS_LIMIT) ";",
- 3),
- /* 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_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"
- ",denoms.fee_deposit_val"
- ",denoms.fee_deposit_frac"
- ",denoms.denom_pub_hash"
- ",wallet_timestamp"
- ",refund_deadline"
- ",wire_deadline"
- ",merchant_pub"
- ",h_contract_terms"
- ",wire_salt"
- ",payto_uri"
- ",coin_sig"
- ",deposit_serial_id"
- ",done"
- " FROM deposits"
- " JOIN wire_targets"
- " USING (wire_target_serial_id)"
- " JOIN known_coins kc"
- " USING (known_coin_id)"
- " JOIN denominations denoms"
- " USING (denominations_serial)"
- " WHERE coin_pub=$1;",
- 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 (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",
- 1),
- /* Used in #postgres_lookup_wire_transfer */
- GNUNET_PQ_make_prepare (
- "lookup_transactions",
- "SELECT"
- " aggregation_serial_id"
- ",deposits.h_contract_terms"
- ",payto_uri"
- ",kc.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 wire_targets"
- " USING (wire_target_serial_id)"
- " JOIN known_coins kc"
- " USING (known_coin_id)"
- " JOIN denominations denom"
- " USING (denominations_serial)"
- " 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"
- ",wire_salt"
- ",payto_uri"
- ",denom.fee_deposit_val"
- ",denom.fee_deposit_frac"
- " FROM deposits"
- " JOIN wire_targets"
- " USING (wire_target_serial_id)"
- " JOIN aggregation_tracking"
- " USING (deposit_serial_id)"
- " JOIN known_coins"
- " USING (known_coin_id)"
- " JOIN denominations denom"
- " USING (denominations_serial)"
- " JOIN wire_out"
- " USING (wtid_raw)"
- " WHERE coin_pub=$1"
- " AND merchant_pub=$3"
- " AND h_contract_terms=$2",
- 3),
- /* 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_serial_id"
- ",exchange_account_section"
- ",amount_val"
- ",amount_frac"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6);",
- 6),
- GNUNET_PQ_make_prepare (
- "insert_into_table_wire_out",
- "INSERT INTO wire_out"
- "(wireout_uuid"
- ",execution_date"
- ",wtid_raw"
- ",wire_target_serial_id"
- ",exchange_account_section"
- ",amount_val"
- ",amount_frac"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7);",
- 7),
- /* 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 "
- "(wire_method"
- ",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_mark_failed() */
- GNUNET_PQ_make_prepare (
- "wire_prepare_data_mark_failed",
- "UPDATE prewire"
- " SET failed=TRUE"
- " WHERE prewire_uuid=$1;",
- 1),
- /* Used in #postgres_wire_prepare_data_get() */
- GNUNET_PQ_make_prepare (
- "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;",
- 2),
- /* 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"
- ",payto_uri"
- ",wire_deadline"
- ",tiny"
- ",done"
- " FROM deposits d"
- " JOIN known_coins"
- " USING (known_coin_id)"
- " JOIN wire_targets"
- " USING (wire_target_serial_id)"
- " WHERE wire_deadline >= $1"
- " AND wire_deadline < $2"
- " AND NOT (EXISTS (SELECT 1"
- " FROM refunds"
- " JOIN deposits dx USING (deposit_serial_id)"
- " WHERE (dx.known_coin_id = d.known_coin_id))"
- " OR EXISTS (SELECT 1"
- " FROM aggregation_tracking"
- " WHERE (aggregation_tracking.deposit_serial_id = d.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"
- ",payto_uri"
- ",amount_val"
- ",amount_frac"
- " FROM wire_out"
- " JOIN wire_targets"
- " USING (wire_target_serial_id)"
- " 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"
- ",payto_uri"
- ",amount_val"
- ",amount_frac"
- " FROM wire_out"
- " JOIN wire_targets"
- " USING (wire_target_serial_id)"
- " WHERE "
- " wireout_uuid>=$1 "
- " AND exchange_account_section=$2"
- " ORDER BY wireout_uuid ASC;",
- 2),
- /* Used in #postgres_select_recoup_above_serial_id() to obtain recoup transactions */
- GNUNET_PQ_make_prepare (
- "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"
- ",denoms.denom_pub"
- ",amount_val"
- ",amount_frac"
- " FROM recoup"
- " JOIN known_coins coins"
- " USING (known_coin_id)"
- " 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;",
- 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"
- ",recoup_timestamp"
- ",old_coins.coin_pub AS old_coin_pub"
- ",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_val"
- ",amount_frac"
- " 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.known_coin_id = recoup_refresh.known_coin_id)"
- " 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;",
- 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"
- ",reserves.reserve_pub"
- ",execution_date"
- ",wtid"
- ",payto_uri AS receiver_account"
- ",amount_val"
- ",amount_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- " FROM reserves_close"
- " JOIN wire_targets"
- " USING (wire_target_serial_id)"
- " JOIN reserves"
- " USING (reserve_pub)"
- " 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"
- " coins.coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",recoup_timestamp"
- ",denoms.denom_pub_hash"
- ",coins.denom_sig"
- " FROM reserves"
- " JOIN reserves_out ro"
- " USING (reserve_uuid)"
- " JOIN recoup"
- " USING (reserve_out_serial_id)"
- " JOIN known_coins coins"
- " USING (known_coin_id)"
- " JOIN denominations denoms"
- " ON (coins.denominations_serial = denoms.denominations_serial)"
- " WHERE reserve_pub=$1;",
- 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"
- " coins.coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",recoup_timestamp"
- ",denoms.denom_pub_hash"
- ",coins.denom_sig"
- ",recoup_refresh_uuid"
- " FROM recoup_refresh"
- " JOIN known_coins coins"
- " USING (known_coin_id)"
- " JOIN denominations denoms"
- " USING (denominations_serial)"
- " WHERE rrc_serial IN"
- " (SELECT rrc.rrc_serial"
- " FROM refresh_commitments"
- " JOIN refresh_revealed_coins rrc"
- " USING (melt_serial_id)"
- " WHERE old_coin_pub=$1);",
- 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"
- ",payto_uri AS receiver_account"
- ",wtid"
- " FROM reserves_close"
- " JOIN wire_targets"
- " USING (wire_target_serial_id)"
- " WHERE reserve_pub=$1;",
- 1),
- /* Used in #postgres_get_expired_reserves() */
- GNUNET_PQ_make_prepare (
- "get_expired_reserves",
- "SELECT"
- " expiration_date"
- ",payto_uri AS account_details"
- ",reserve_pub"
- ",current_balance_val"
- ",current_balance_frac"
- " FROM reserves"
- " JOIN reserves_in ri"
- " USING (reserve_pub)"
- " JOIN wire_targets wt"
- " ON (ri.wire_source_serial_id = wt.wire_target_serial_id)"
- " 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"
- " reserves.reserve_pub"
- ",denoms.denom_pub_hash"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",recoup_timestamp"
- ",recoup_uuid"
- " FROM recoup"
- " JOIN reserves_out ro"
- " USING (reserve_out_serial_id)"
- " JOIN reserves"
- " USING (reserve_uuid)"
- " JOIN known_coins coins"
- " USING (known_coin_id)"
- " JOIN denominations denoms"
- " ON (denoms.denominations_serial = coins.denominations_serial)"
- " WHERE coins.coin_pub=$1;",
- 1),
- /* Used in #postgres_get_coin_transactions() to obtain recoup transactions
- for a refreshed coin */
- GNUNET_PQ_make_prepare (
- "recoup_by_refreshed_coin",
- "SELECT"
- " old_coins.coin_pub AS old_coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",recoup_timestamp"
- ",denoms.denom_pub_hash"
- ",coins.denom_sig"
- ",recoup_refresh_uuid"
- " FROM recoup_refresh"
- " 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 (recoup_refresh.known_coin_id = coins.known_coin_id)"
- " JOIN denominations denoms"
- " ON (denoms.denominations_serial = coins.denominations_serial)"
- " WHERE coins.coin_pub=$1;",
- 1),
- /* Used in #postgres_get_reserve_by_h_blind() */
- GNUNET_PQ_make_prepare (
- "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;",
- 1),
- /* Used in #postgres_get_old_coin_by_h_blind() */
- GNUNET_PQ_make_prepare (
- "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;",
- 1),
- /* Used in #postgres_lookup_auditor_timestamp() */
- GNUNET_PQ_make_prepare (
- "lookup_auditor_timestamp",
- "SELECT"
- " last_change"
- " FROM auditors"
- " WHERE auditor_pub=$1;",
- 1),
- /* Used in #postgres_lookup_auditor_status() */
- GNUNET_PQ_make_prepare (
- "lookup_auditor_status",
- "SELECT"
- " auditor_url"
- ",is_active"
- " FROM auditors"
- " WHERE auditor_pub=$1;",
- 1),
- /* Used in #postgres_lookup_wire_timestamp() */
- GNUNET_PQ_make_prepare (
- "lookup_wire_timestamp",
- "SELECT"
- " last_change"
- " FROM wire_accounts"
- " WHERE payto_uri=$1;",
- 1),
- /* used in #postgres_insert_auditor() */
- GNUNET_PQ_make_prepare (
- "insert_auditor",
- "INSERT INTO auditors "
- "(auditor_pub"
- ",auditor_name"
- ",auditor_url"
- ",is_active"
- ",last_change"
- ") VALUES "
- "($1, $2, $3, true, $4);",
- 4),
- /* used in #postgres_update_auditor() */
- GNUNET_PQ_make_prepare (
- "update_auditor",
- "UPDATE auditors"
- " SET"
- " auditor_url=$2"
- " ,auditor_name=$3"
- " ,is_active=$4"
- " ,last_change=$5"
- " WHERE auditor_pub=$1",
- 5),
- /* used in #postgres_insert_wire() */
- GNUNET_PQ_make_prepare (
- "insert_wire",
- "INSERT INTO wire_accounts "
- "(payto_uri"
- ",master_sig"
- ",is_active"
- ",last_change"
- ") VALUES "
- "($1, $2, true, $3);",
- 3),
- /* used in #postgres_update_wire() */
- GNUNET_PQ_make_prepare (
- "update_wire",
- "UPDATE wire_accounts"
- " SET"
- " is_active=$2"
- " ,last_change=$3"
- " WHERE payto_uri=$1",
- 3),
- /* used in #postgres_update_wire() */
- GNUNET_PQ_make_prepare (
- "get_wire_accounts",
- "SELECT"
- " payto_uri"
- ",master_sig"
- " FROM wire_accounts"
- " WHERE is_active",
- 0),
- /* used in #postgres_update_wire() */
- GNUNET_PQ_make_prepare (
- "get_wire_fees",
- "SELECT"
- " wire_fee_val"
- ",wire_fee_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",start_date"
- ",end_date"
- ",master_sig"
- " FROM wire_fee"
- " WHERE wire_method=$1",
- 1),
- /* used in #postgres_insert_signkey_revocation() */
- GNUNET_PQ_make_prepare (
- "insert_signkey_revocation",
- "INSERT INTO signkey_revocations "
- "(esk_serial"
- ",master_sig"
- ") SELECT esk_serial, $2 "
- " FROM exchange_sign_keys"
- " WHERE exchange_pub=$1;",
- 2),
- /* used in #postgres_insert_signkey_revocation() */
- GNUNET_PQ_make_prepare (
- "lookup_signkey_revocation",
- "SELECT "
- " master_sig"
- " FROM signkey_revocations"
- " WHERE esk_serial="
- " (SELECT esk_serial"
- " FROM exchange_sign_keys"
- " WHERE exchange_pub=$1);",
- 1),
- /* used in #postgres_insert_signkey() */
- GNUNET_PQ_make_prepare (
- "insert_signkey",
- "INSERT INTO exchange_sign_keys "
- "(exchange_pub"
- ",valid_from"
- ",expire_sign"
- ",expire_legal"
- ",master_sig"
- ") VALUES "
- "($1, $2, $3, $4, $5);",
- 5),
- /* used in #postgres_lookup_signing_key() */
- GNUNET_PQ_make_prepare (
- "lookup_signing_key",
- "SELECT"
- " valid_from"
- ",expire_sign"
- ",expire_legal"
- " FROM exchange_sign_keys"
- " WHERE exchange_pub=$1",
- 1),
- /* used in #postgres_lookup_denomination_key() */
- GNUNET_PQ_make_prepare (
- "lookup_denomination_key",
- "SELECT"
- " 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 denominations"
- " WHERE denom_pub_hash=$1;",
- 1),
- /* used in #postgres_insert_auditor_denom_sig() */
- GNUNET_PQ_make_prepare (
- "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;",
- 3),
- /* used in #postgres_select_auditor_denom_sig() */
- GNUNET_PQ_make_prepare (
- "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);",
- 2),
- /* used in #postgres_lookup_wire_fee_by_time() */
- GNUNET_PQ_make_prepare (
- "lookup_wire_fee_by_time",
- "SELECT"
- " wire_fee_val"
- ",wire_fee_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- " FROM wire_fee"
- " WHERE wire_method=$1"
- " AND end_date > $2"
- " AND start_date < $3;",
- 1),
- /* used in #postgres_commit */
- GNUNET_PQ_make_prepare (
- "do_commit",
- "COMMIT",
- 0),
- /* used in #postgres_lookup_serial_by_table() */
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_denominations",
- "SELECT"
- " denominations_serial AS serial"
- " FROM denominations"
- " ORDER BY denominations_serial DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "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;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_reserves",
- "SELECT"
- " reserve_uuid AS serial"
- " FROM reserves"
- " ORDER BY reserve_uuid DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "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;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_reserves_close",
- "SELECT"
- " close_uuid AS serial"
- " FROM reserves_close"
- " ORDER BY close_uuid DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "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;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_auditors",
- "SELECT"
- " auditor_uuid AS serial"
- " FROM auditors"
- " ORDER BY auditor_uuid DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "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;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_exchange_sign_keys",
- "SELECT"
- " esk_serial AS serial"
- " FROM exchange_sign_keys"
- " ORDER BY esk_serial DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "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;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_known_coins",
- "SELECT"
- " known_coin_id AS serial"
- " FROM known_coins"
- " ORDER BY known_coin_id DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_refresh_commitments",
- "SELECT"
- " melt_serial_id AS serial"
- " FROM refresh_commitments"
- " ORDER BY melt_serial_id DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_refresh_revealed_coins",
- "SELECT"
- " rrc_serial AS serial"
- " FROM refresh_revealed_coins"
- " ORDER BY rrc_serial DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_refresh_transfer_keys",
- "SELECT"
- " rtc_serial AS serial"
- " FROM refresh_transfer_keys"
- " ORDER BY rtc_serial DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_deposits",
- "SELECT"
- " deposit_serial_id AS serial"
- " FROM deposits"
- " ORDER BY deposit_serial_id DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_refunds",
- "SELECT"
- " refund_serial_id AS serial"
- " FROM refunds"
- " ORDER BY refund_serial_id DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_wire_out",
- "SELECT"
- " wireout_uuid AS serial"
- " FROM wire_out"
- " ORDER BY wireout_uuid DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_aggregation_tracking",
- "SELECT"
- " aggregation_serial_id AS serial"
- " FROM aggregation_tracking"
- " ORDER BY aggregation_serial_id DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_wire_fee",
- "SELECT"
- " wire_fee_serial AS serial"
- " FROM wire_fee"
- " ORDER BY wire_fee_serial DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_recoup",
- "SELECT"
- " recoup_uuid AS serial"
- " FROM recoup"
- " ORDER BY recoup_uuid DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare (
- "select_serial_by_table_recoup_refresh",
- "SELECT"
- " recoup_refresh_uuid AS serial"
- " FROM recoup_refresh"
- " ORDER BY recoup_refresh_uuid DESC"
- " LIMIT 1;",
- 0),
- /* For postgres_lookup_records_by_table */
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_denominations",
- "SELECT"
- " denominations_serial AS serial"
- ",denom_type"
- ",age_restrictions"
- ",denom_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 denominations"
- " WHERE denominations_serial > $1"
- " ORDER BY denominations_serial ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "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;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_reserves",
- "SELECT"
- " reserve_uuid AS serial"
- ",reserve_pub"
- ",current_balance_val"
- ",current_balance_frac"
- ",expiration_date"
- ",gc_date"
- " FROM reserves"
- " WHERE reserve_uuid > $1"
- " ORDER BY reserve_uuid ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_reserves_in",
- "SELECT"
- " reserve_in_serial_id AS serial"
- ",reserve_pub"
- ",wire_reference"
- ",credit_val"
- ",credit_frac"
- ",wire_source_serial_id"
- ",exchange_account_section"
- ",execution_date"
- " FROM reserves_in"
- " WHERE reserve_in_serial_id > $1"
- " ORDER BY reserve_in_serial_id ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_reserves_close",
- "SELECT"
- " close_uuid AS serial"
- ",reserve_pub"
- ",execution_date"
- ",wtid"
- ",wire_target_serial_id"
- ",amount_val"
- ",amount_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- " FROM reserves_close"
- " WHERE close_uuid > $1"
- " ORDER BY close_uuid ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_reserves_out",
- "SELECT"
- " reserve_out_serial_id AS serial"
- ",h_blind_ev"
- ",denominations_serial"
- ",denom_sig"
- ",reserve_pub"
- ",reserve_sig"
- ",execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- " FROM reserves_out"
- " JOIN reserves USING (reserve_uuid)"
- " WHERE reserve_out_serial_id > $1"
- " ORDER BY reserve_out_serial_id ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "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;",
- 1),
- GNUNET_PQ_make_prepare (
- "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;",
- 1),
- GNUNET_PQ_make_prepare (
- "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;",
- 1),
- GNUNET_PQ_make_prepare (
- "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;",
- 1),
- GNUNET_PQ_make_prepare (
- "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;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_refresh_commitments",
- "SELECT"
- " melt_serial_id AS serial"
- ",rc"
- ",old_coin_sig"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",noreveal_index"
- ",old_coin_pub"
- " FROM refresh_commitments"
- " WHERE melt_serial_id > $1"
- " ORDER BY melt_serial_id ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_refresh_revealed_coins",
- "SELECT"
- " rrc_serial AS serial"
- ",freshcoin_index"
- ",link_sig"
- ",coin_ev"
- ",ev_sig"
- ",denominations_serial"
- ",melt_serial_id"
- " FROM refresh_revealed_coins"
- " WHERE rrc_serial > $1"
- " ORDER BY rrc_serial ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "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;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_deposits",
- "SELECT"
- " deposit_serial_id AS serial"
- ",shard"
- ",known_coin_id"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",wallet_timestamp"
- ",exchange_timestamp"
- ",refund_deadline"
- ",wire_deadline"
- ",merchant_pub"
- ",h_contract_terms"
- ",coin_sig"
- ",wire_salt"
- ",wire_target_serial_id"
- ",tiny"
- ",done"
- ",extension_blocked"
- ",extension_details_serial_id"
- " FROM deposits"
- " WHERE deposit_serial_id > $1"
- " ORDER BY deposit_serial_id ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_refunds",
- "SELECT"
- " refund_serial_id AS serial"
- ",merchant_sig"
- ",rtransaction_id"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",deposit_serial_id"
- " FROM refunds"
- " WHERE refund_serial_id > $1"
- " ORDER BY refund_serial_id ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_wire_out",
- "SELECT"
- " wireout_uuid AS serial"
- ",execution_date"
- ",wtid_raw"
- ",wire_target_serial_id"
- ",exchange_account_section"
- ",amount_val"
- ",amount_frac"
- " FROM wire_out"
- " WHERE wireout_uuid > $1"
- " ORDER BY wireout_uuid ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_aggregation_tracking",
- "SELECT"
- " aggregation_serial_id AS serial"
- ",deposit_serial_id"
- ",wtid_raw"
- " FROM aggregation_tracking"
- " WHERE aggregation_serial_id > $1"
- " ORDER BY aggregation_serial_id ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_wire_fee",
- "SELECT"
- " wire_fee_serial AS serial"
- ",wire_method"
- ",start_date"
- ",end_date"
- ",wire_fee_val"
- ",wire_fee_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",master_sig"
- " FROM wire_fee"
- " WHERE wire_fee_serial > $1"
- " ORDER BY wire_fee_serial ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_recoup",
- "SELECT"
- " recoup_uuid AS serial"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",recoup_timestamp"
- ",known_coin_id"
- ",reserve_out_serial_id"
- " FROM recoup"
- " WHERE recoup_uuid > $1"
- " ORDER BY recoup_uuid ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_recoup_refresh",
- "SELECT"
- " recoup_refresh_uuid AS serial"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",recoup_timestamp"
- ",known_coin_id"
- ",rrc_serial"
- " FROM recoup_refresh"
- " WHERE recoup_refresh_uuid > $1"
- " ORDER BY recoup_refresh_uuid ASC;",
- 1),
- /* For postgres_insert_records_by_table */
- GNUNET_PQ_make_prepare (
- "insert_into_table_denominations",
- "INSERT INTO denominations"
- "(denominations_serial"
- ",denom_pub_hash"
- ",denom_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"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
- " $11, $12, $13, $14, $15, $16, $17, $18);",
- 18),
- GNUNET_PQ_make_prepare (
- "insert_into_table_denomination_revocations",
- "INSERT INTO denomination_revocations"
- "(denom_revocations_serial_id"
- ",master_sig"
- ",denominations_serial"
- ") VALUES "
- "($1, $2, $3);",
- 3),
- GNUNET_PQ_make_prepare (
- "insert_into_table_reserves",
- "INSERT INTO reserves"
- "(reserve_uuid"
- ",reserve_pub"
- ",current_balance_val"
- ",current_balance_frac"
- ",expiration_date"
- ",gc_date"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6);",
- 6),
- GNUNET_PQ_make_prepare (
- "insert_into_table_reserves_in",
- "INSERT INTO reserves_in"
- "(reserve_in_serial_id"
- ",wire_reference"
- ",credit_val"
- ",credit_frac"
- ",wire_source_serial_id"
- ",exchange_account_section"
- ",execution_date"
- ",reserve_pub"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8);",
- 8),
- GNUNET_PQ_make_prepare (
- "insert_into_table_reserves_close",
- "INSERT INTO reserves_close"
- "(close_uuid"
- ",execution_date"
- ",wtid"
- ",wire_target_serial_id"
- ",amount_val"
- ",amount_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",reserve_pub"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8, $9);",
- 9),
- GNUNET_PQ_make_prepare (
- "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_val"
- ",amount_with_fee_frac"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8, $9);",
- 9),
- GNUNET_PQ_make_prepare (
- "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);",
- 6),
- GNUNET_PQ_make_prepare (
- "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);",
- 4),
- GNUNET_PQ_make_prepare (
- "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);",
- 6),
- GNUNET_PQ_make_prepare (
- "insert_into_table_signkey_revocations",
- "INSERT INTO signkey_revocations"
- "(signkey_revocations_serial_id"
- ",esk_serial"
- ",master_sig"
- ") VALUES "
- "($1, $2, $3);",
- 3),
- GNUNET_PQ_make_prepare (
- "insert_into_table_known_coins",
- "INSERT INTO known_coins"
- "(known_coin_id"
- ",coin_pub"
- ",denom_sig"
- ",denominations_serial"
- ") VALUES "
- "($1, $2, $3, $4);",
- 4),
- GNUNET_PQ_make_prepare (
- "insert_into_table_refresh_commitments",
- "INSERT INTO refresh_commitments"
- "(melt_serial_id"
- ",rc"
- ",old_coin_sig"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",noreveal_index"
- ",old_coin_pub"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7);",
- 7),
- GNUNET_PQ_make_prepare (
- "insert_into_table_refresh_revealed_coins",
- "INSERT INTO refresh_revealed_coins"
- "(rrc_serial"
- ",freshcoin_index"
- ",link_sig"
- ",coin_ev"
- ",h_coin_ev"
- ",ev_sig"
- ",denominations_serial"
- ",melt_serial_id"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8);",
- 8),
- GNUNET_PQ_make_prepare (
- "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);",
- 4),
- GNUNET_PQ_make_prepare (
- "insert_into_table_deposits",
- "INSERT INTO deposits"
- "(deposit_serial_id"
- ",shard"
- ",known_coin_id"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",wallet_timestamp"
- ",exchange_timestamp"
- ",refund_deadline"
- ",wire_deadline"
- ",merchant_pub"
- ",h_contract_terms"
- ",coin_sig"
- ",wire_salt"
- ",wire_target_serial_id"
- ",tiny"
- ",done"
- ",extension_blocked"
- ",extension_details_serial_id"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
- " $11, $12, $13, $14, $15, $16, $17, $18);",
- 18),
- GNUNET_PQ_make_prepare (
- "insert_into_table_refunds",
- "INSERT INTO refunds"
- "(refund_serial_id"
- ",merchant_sig"
- ",rtransaction_id"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",deposit_serial_id"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6);",
- 6),
- GNUNET_PQ_make_prepare (
- "insert_into_table_aggregation_tracking",
- "INSERT INTO aggregation_tracking"
- "(aggregation_serial_id"
- ",deposit_serial_id"
- ",wtid_raw"
- ") VALUES "
- "($1, $2, $3);",
- 3),
- GNUNET_PQ_make_prepare (
- "insert_into_table_wire_fee",
- "INSERT INTO wire_fee"
- "(wire_fee_serial"
- ",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, $9);",
- 9),
- GNUNET_PQ_make_prepare (
- "insert_into_table_recoup",
- "INSERT INTO recoup"
- "(recoup_uuid"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",recoup_timestamp"
- ",known_coin_id"
- ",reserve_out_serial_id"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8);",
- 8),
- GNUNET_PQ_make_prepare (
- "insert_into_table_recoup_refresh",
- "INSERT INTO recoup_refresh"
- "(recoup_refresh_uuid"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",recoup_timestamp"
- ",known_coin_id"
- ",rrc_serial"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8);",
- 8),
-
- /* Used in #postgres_begin_shard() */
- GNUNET_PQ_make_prepare (
- "get_open_shard",
- "SELECT"
- " start_row"
- ",end_row"
- " FROM work_shards"
- " WHERE job_name=$1"
- " AND last_attempt<$2"
- " AND completed=FALSE"
- " ORDER BY last_attempt ASC"
- " LIMIT 1;",
- 2),
- /* Used in #postgres_begin_revolving_shard() */
- GNUNET_PQ_make_prepare (
- "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;",
- 2),
- /* Used in #postgres_begin_shard() */
- GNUNET_PQ_make_prepare (
- "reclaim_shard",
- "UPDATE work_shards"
- " SET last_attempt=$2"
- " WHERE job_name=$1"
- " AND start_row=$3"
- " AND end_row=$4",
- 4),
- /* Used in #postgres_begin_revolving_shard() */
- GNUNET_PQ_make_prepare (
- "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",
- 4),
- /* Used in #postgres_begin_shard() */
- GNUNET_PQ_make_prepare (
- "get_last_shard",
- "SELECT"
- " end_row"
- " FROM work_shards"
- " WHERE job_name=$1"
- " ORDER BY end_row DESC"
- " LIMIT 1;",
- 1),
- /* Used in #postgres_begin_revolving_shard() */
- GNUNET_PQ_make_prepare (
- "get_last_revolving_shard",
- "SELECT"
- " end_row"
- " FROM revolving_work_shards"
- " WHERE job_name=$1"
- " ORDER BY end_row DESC"
- " LIMIT 1;",
- 1),
- /* Used in #postgres_begin_shard() */
- GNUNET_PQ_make_prepare (
- "claim_next_shard",
- "INSERT INTO work_shards"
- "(job_name"
- ",last_attempt"
- ",start_row"
- ",end_row"
- ") VALUES "
- "($1, $2, $3, $4);",
- 4),
- /* Used in #postgres_claim_revolving_shard() */
- GNUNET_PQ_make_prepare (
- "create_revolving_shard",
- "INSERT INTO revolving_work_shards"
- "(job_name"
- ",last_attempt"
- ",start_row"
- ",end_row"
- ",active"
- ") VALUES "
- "($1, $2, $3, $4, TRUE);",
- 4),
- /* Used in #postgres_complete_shard() */
- GNUNET_PQ_make_prepare (
- "complete_shard",
- "UPDATE work_shards"
- " SET completed=TRUE"
- " WHERE job_name=$1"
- " AND start_row=$2"
- " AND end_row=$3",
- 3),
- /* Used in #postgres_complete_shard() */
- GNUNET_PQ_make_prepare (
- "release_revolving_shard",
- "UPDATE revolving_work_shards"
- " SET active=FALSE"
- " WHERE job_name=$1"
- " AND start_row=$2"
- " AND end_row=$3",
- 3),
- /* Used in #postgres_set_extension_config */
- GNUNET_PQ_make_prepare (
- "set_extension_config",
- "INSERT INTO extensions (name, config, config_sig) VALUES ($1, $2, $3) "
- "ON CONFLICT (name) "
- "DO UPDATE SET (config, config_sig) = ($2, $3)",
- 3),
- /* Used in #postgres_get_extension_config */
- GNUNET_PQ_make_prepare (
- "get_extension_config",
- "SELECT (config) FROM extensions"
- " WHERE name=$1;",
- 1),
- GNUNET_PQ_PREPARED_STATEMENT_END
- };
-
- ret = GNUNET_PQ_prepare_statements (pg->conn,
- ps);
- if (GNUNET_OK != ret)
- return ret;
- pg->init = true;
- return GNUNET_OK;
-}
-
-
-/**
* Connect to the database if the connection does not exist yet.
*
* @param pg the plugin-specific state
- * @param skip_prepare true if we should skip prepared statement setup
* @return #GNUNET_OK on success
*/
-static enum GNUNET_GenericReturnValue
-internal_setup (struct PostgresClosure *pg,
- bool skip_prepare)
+enum GNUNET_GenericReturnValue
+TEH_PG_internal_setup (struct PostgresClosure *pg)
{
if (NULL == pg->conn)
{
@@ -2795,6 +272,9 @@ internal_setup (struct PostgresClosure *pg,
"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
@@ -2803,7 +283,9 @@ internal_setup (struct PostgresClosure *pg,
"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 autocommit=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
@@ -2816,8656 +298,17 @@ internal_setup (struct PostgresClosure *pg,
NULL);
if (NULL == db_conn)
return GNUNET_SYSERR;
+
+ pg->prep_gen++;
pg->conn = db_conn;
}
if (NULL == pg->transaction_name)
GNUNET_PQ_reconnect_if_down (pg->conn);
- if (pg->init)
- return GNUNET_OK;
- if (skip_prepare)
- return GNUNET_OK;
- return prepare_statements (pg);
-}
-
-
-/**
- * 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
- */
-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
- };
-
- if (! pg->init)
- {
- if (GNUNET_OK !=
- internal_setup (pg,
- false))
- 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;
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_GenericReturnValue
-postgres_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
- };
-
- if (GNUNET_SYSERR ==
- postgres_preflight (pg))
- return GNUNET_SYSERR;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Starting transaction named %s on %p\n",
- name,
- pg->conn);
- 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;
}
/**
- * 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
- */
-static enum GNUNET_GenericReturnValue
-postgres_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
- };
-
- if (GNUNET_SYSERR ==
- postgres_preflight (pg))
- return GNUNET_SYSERR;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Starting transaction named %s on %p\n",
- name,
- pg->conn);
- 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;
-}
-
-
-/**
- * Roll back the current transaction of a database connection.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- */
-static void
-postgres_rollback (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("ROLLBACK"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Rolling back transaction on %p\n",
- pg->conn);
- GNUNET_break (GNUNET_OK ==
- GNUNET_PQ_exec_statements (pg->conn,
- es));
- pg->transaction_name = NULL;
-}
-
-
-/**
- * Commit the current transaction of a database connection.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @return final transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_commit (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "do_commit",
- params);
- pg->transaction_name = NULL;
- return qs;
-}
-
-
-/**
- * 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
- */
-static struct GNUNET_DB_EventHandler *
-postgres_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);
-}
-
-
-/**
- * Stop notifications.
- *
- * @param cls the plugin's `struct PostgresClosure`
- * @param eh handle to unregister.
- */
-static void
-postgres_event_listen_cancel (void *cls,
- struct GNUNET_DB_EventHandler *eh)
-{
- (void) cls;
- GNUNET_PQ_event_listen_cancel (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
- */
-static void
-postgres_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);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_denomination_info (
- void *cls,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&issue->properties.denom_hash),
- TALER_PQ_query_param_denom_pub (denom_pub),
- GNUNET_PQ_query_param_auto_from_type (&issue->signature),
- GNUNET_PQ_query_param_timestamp_nbo (&issue->properties.start),
- GNUNET_PQ_query_param_timestamp_nbo (&issue->properties.expire_withdraw),
- GNUNET_PQ_query_param_timestamp_nbo (&issue->properties.expire_deposit),
- GNUNET_PQ_query_param_timestamp_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
- };
-
- GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
- GNUNET_TIME_timestamp_ntoh (
- issue->properties.start).abs_time));
- GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
- GNUNET_TIME_timestamp_ntoh (
- issue->properties.expire_withdraw).abs_time));
- GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
- GNUNET_TIME_timestamp_ntoh (
- issue->properties.expire_deposit).abs_time));
- GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
- GNUNET_TIME_timestamp_ntoh (
- issue->properties.expire_legal).abs_time));
- /* 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 (pg->conn,
- "denomination_insert",
- params);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_denomination_info (
- void *cls,
- const struct TALER_DenominationHash *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_sig",
- &issue->signature),
- GNUNET_PQ_result_spec_timestamp_nbo ("valid_from",
- &issue->properties.start),
- GNUNET_PQ_result_spec_timestamp_nbo ("expire_withdraw",
- &issue->properties.expire_withdraw),
- GNUNET_PQ_result_spec_timestamp_nbo ("expire_deposit",
- &issue->properties.expire_deposit),
- GNUNET_PQ_result_spec_timestamp_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
- };
-
- memset (&issue->properties.master,
- 0,
- sizeof (issue->properties.master));
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->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_sig",
- &issue.signature),
- GNUNET_PQ_result_spec_timestamp_nbo ("valid_from",
- &issue.properties.start),
- GNUNET_PQ_result_spec_timestamp_nbo ("expire_withdraw",
- &issue.properties.expire_withdraw),
- GNUNET_PQ_result_spec_timestamp_nbo ("expire_deposit",
- &issue.properties.expire_deposit),
- GNUNET_PQ_result_spec_timestamp_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),
- TALER_PQ_result_spec_denom_pub ("denom_pub",
- &denom_pub),
- GNUNET_PQ_result_spec_end
- };
-
- memset (&issue.properties.master,
- 0,
- sizeof (issue.properties.master));
- 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);
- TALER_denom_pub_hash (&denom_pub,
- &issue.properties.denom_hash);
- 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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
- };
-
- return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "denomination_iterate",
- params,
- &domination_cb_helper,
- &dic);
-}
-
-
-/**
- * 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 #postgres_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;
- struct TALER_DenominationPublicKey denom_pub;
- struct TALER_MasterSignatureP master_sig;
- struct TALER_DenominationHash h_denom_pub;
- bool revoked;
- struct GNUNET_PQ_ResultSpec rs[] = {
- 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.fee_withdraw),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- &meta.fee_deposit),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
- &meta.fee_refresh),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
- &meta.fee_refund),
- TALER_PQ_result_spec_denom_pub ("denom_pub",
- &denom_pub),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- return;
- }
- 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);
- }
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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
- };
-
- return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "select_denominations",
- params,
- &dominations_cb_helper,
- &dic);
-}
-
-
-/**
- * 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 #postgres_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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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,
- };
-
- now = GNUNET_TIME_absolute_get ();
- return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "select_signkeys",
- params,
- &signkeys_cb_helper,
- &dic);
-}
-
-
-/**
- * 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 #postgres_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);
- }
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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,
- };
-
- return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "select_auditors",
- params,
- &auditors_cb_helper,
- &dic);
-}
-
-
-/**
- * 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 #postgres_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_DenominationHash 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);
- }
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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,
- };
-
- return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "select_auditor_denoms",
- params,
- &auditor_denoms_cb_helper,
- &dic);
-}
-
-
-/**
- * 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.
- * @param[out] kyc set to the KYC status of the reserve
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_reserves_get (void *cls,
- struct TALER_EXCHANGEDB_Reserve *reserve,
- struct TALER_EXCHANGEDB_KycStatus *kyc)
-{
- 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),
- GNUNET_PQ_result_spec_timestamp ("expiration_date",
- &reserve->expiry),
- GNUNET_PQ_result_spec_timestamp ("gc_date",
- &reserve->gc),
- GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
- &kyc->payment_target_uuid),
- GNUNET_PQ_result_spec_bool ("kyc_ok",
- &kyc->ok),
- GNUNET_PQ_result_spec_end
- };
-
- kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "reserves_get_with_kyc",
- params,
- rs);
-}
-
-
-/**
- * Set the KYC status to "OK" for a bank account.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param payment_target_uuid which account has been checked
- * @param id external ID to persist
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_set_kyc_ok (void *cls,
- uint64_t payment_target_uuid,
- const char *id)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&payment_target_uuid),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_QueryParam params2[] = {
- GNUNET_PQ_query_param_uint64 (&payment_target_uuid),
- GNUNET_PQ_query_param_string (id),
- GNUNET_PQ_query_param_end
- };
- struct TALER_KycCompletedEventP rep = {
- .header.size = htons (sizeof (rep)),
- .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED)
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("h_payto",
- &rep.h_payto),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "set_kyc_ok",
- params2);
- if (qs <= 0)
- return qs;
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_kyc_h_payto",
- params,
- rs);
- if (qs <= 0)
- return qs;
- postgres_event_notify (pg,
- &rep.header,
- NULL,
- 0);
- return qs;
-}
-
-
-/**
- * Get the @a kyc status and @a h_payto by UUID.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param payment_target_uuid which account to get the KYC status for
- * @param[out] h_payto set to the hash of the account's payto URI (unsalted)
- * @param[out] kyc set to the KYC status of the account
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_kyc_status (void *cls,
- uint64_t payment_target_uuid,
- struct TALER_PaytoHash *h_payto,
- struct TALER_EXCHANGEDB_KycStatus *kyc)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&payment_target_uuid),
- 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_auto_from_type ("kyc_ok",
- &kyc->ok),
- GNUNET_PQ_result_spec_end
- };
-
- kyc->type = TALER_EXCHANGEDB_KYC_UNKNOWN;
- kyc->payment_target_uuid = payment_target_uuid;
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "select_kyc_status",
- params,
- rs);
-}
-
-
-/**
- * Get the KYC status for a wallet. If the status is unknown,
- * inserts a new status record (hence INsertSELECT).
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param payto_uri the payto URI to check
- * @param oauth_username user ID to store
- * @param[out] kyc set to the KYC status of the wallet
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-inselect_account_kyc_status (
- struct PostgresClosure *pg,
- const char *payto_uri,
- struct TALER_EXCHANGEDB_KycStatus *kyc)
-{
-
- struct TALER_PaytoHash h_payto;
- enum GNUNET_DB_QueryStatus qs;
-
- TALER_payto_hash (payto_uri,
- &h_payto);
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&h_payto),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("wire_target_serial_id",
- &kyc->payment_target_uuid),
- GNUNET_PQ_result_spec_bool ("kyc_ok",
- &kyc->ok),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "select_kyc_status_by_payto",
- params,
- rs);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- 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
- };
- struct GNUNET_PQ_ResultSpec irs[] = {
- GNUNET_PQ_result_spec_uint64 ("wire_target_serial_id",
- &kyc->payment_target_uuid),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "insert_kyc_status",
- iparams,
- irs);
- if (qs < 0)
- return qs;
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- return GNUNET_DB_STATUS_SOFT_ERROR;
- kyc->ok = false;
- }
- }
- kyc->type = TALER_EXCHANGEDB_KYC_BALANCE;
- return qs;
-}
-
-
-/**
- * Get the KYC status for a wallet. If the status is unknown,
- * inserts a new status record (hence INsertSELECT).
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param reserve_pub public key of the wallet
- * @param[out] kyc set to the KYC status of the wallet
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_inselect_wallet_kyc_status (
- void *cls,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- struct TALER_EXCHANGEDB_KycStatus *kyc)
-{
- struct PostgresClosure *pg = cls;
- char *payto_uri;
- enum GNUNET_DB_QueryStatus qs;
-
- payto_uri = TALER_payto_from_reserve (pg->exchange_url,
- reserve_pub);
- qs = inselect_account_kyc_status (pg,
- payto_uri,
- kyc);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Wire account for `%s' is %llu\n",
- payto_uri,
- (unsigned long long) kyc->payment_target_uuid);
- GNUNET_free (payto_uri);
- return qs;
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-reserves_get_internal (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",
- &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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->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 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,
- 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 (&reserve->balance),
- GNUNET_PQ_query_param_auto_from_type (&reserve->pub),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "reserve_update",
- params);
-}
-
-
-/**
- * Generate event notification for the reserve
- * change.
- *
- * @param pg plugin state
- * @param reserve_pub reserve to notfiy on
- */
-static void
-notify_on_reserve (struct PostgresClosure *pg,
- 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
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Notifying on reserve!\n");
- postgres_event_notify (pg,
- &rep.header,
- NULL,
- 0);
-}
-
-
-/**
- * Insert an incoming transaction into reserves. New reserves are also
- * created through this function. Started within the scope of an ongoing
- * transaction.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @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,
- 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_section,
- uint64_t wire_ref)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus qs1;
- struct TALER_EXCHANGEDB_Reserve reserve;
- struct GNUNET_TIME_Timestamp expiry;
- struct GNUNET_TIME_Timestamp gc;
- uint64_t reserve_uuid;
-
- reserve.pub = *reserve_pub;
- expiry = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (execution_time.abs_time,
- pg->idle_reserve_expiration_time));
- gc = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (),
- pg->legal_reserve_expiration_time));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "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));
- /* Optimistically assume this is a 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 needs the reserve entry as a foreign key. */
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- TALER_PQ_query_param_amount (balance),
- GNUNET_PQ_query_param_timestamp (&expiry),
- GNUNET_PQ_query_param_timestamp (&gc),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("reserve_uuid",
- &reserve_uuid),
- GNUNET_PQ_result_spec_end
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Reserve does not exist; creating a new one\n");
- /* Note: query uses 'on conflict do nothing' */
- qs1 = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "reserve_create",
- params,
- rs);
- if (qs1 < 0)
- return qs1;
- }
-
- /* Create new incoming transaction, "ON CONFLICT DO NOTHING"
- is again used to guard against duplicates. */
- {
- enum GNUNET_DB_QueryStatus qs2;
- struct TALER_EXCHANGEDB_KycStatus kyc;
- enum GNUNET_DB_QueryStatus qs3;
-
- memset (&kyc,
- 0,
- sizeof (kyc));
- qs3 = inselect_account_kyc_status (pg,
- sender_account_details,
- &kyc);
- if (qs3 <= 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs3);
- return qs3;
- }
- GNUNET_assert (0 != kyc.payment_target_uuid);
- /* We do not have the UUID, so insert by public key */
- 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_uint64 (&kyc.payment_target_uuid),
- GNUNET_PQ_query_param_timestamp (&execution_time),
- GNUNET_PQ_query_param_end
- };
-
- qs2 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "reserves_in_add_transaction",
- params);
- /* qs2 could be 0 as statement used 'ON CONFLICT DO NOTHING' */
- if (0 >= qs2)
- {
- if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs2) &&
- (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs1) )
- {
- /* Conflict for the transaction, but the reserve was
- just now created, that should be impossible. */
- GNUNET_break (0); /* should be impossible: reserve was fresh,
- but transaction already known */
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- /* Transaction was already known or error. We are finished. */
- return qs2;
- }
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs1)
- {
- /* New reserve, we are finished */
- notify_on_reserve (pg,
- reserve_pub);
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
- }
-
- /* we were wrong with our optimistic assumption:
- reserve did already exist, need to do an update instead */
- {
- /* We need to move away from 'read committed' to serializable.
- Also, we know that it should be safe to commit at this point.
- (We are only run in a larger transaction for performance.) */
- enum GNUNET_DB_QueryStatus cs;
-
- cs = postgres_commit (pg);
- if (cs < 0)
- return cs;
- if (GNUNET_OK !=
- postgres_start (pg,
- "reserve-update-serializable"))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- {
- enum GNUNET_DB_QueryStatus reserve_exists;
-
- reserve_exists = reserves_get_internal (pg,
- &reserve);
- switch (reserve_exists)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- return reserve_exists;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return reserve_exists;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* First we got a conflict, but then we cannot select? Very strange. */
- GNUNET_break (0);
- return GNUNET_DB_STATUS_SOFT_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* continued below */
- break;
- }
- }
-
- {
- struct TALER_EXCHANGEDB_Reserve updated_reserve;
- enum GNUNET_DB_QueryStatus qs3;
-
- /* 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. */
- updated_reserve.pub = reserve.pub;
- if (0 >
- 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_timestamp_max (expiry,
- reserve.expiry);
- updated_reserve.gc = GNUNET_TIME_timestamp_max (gc,
- reserve.gc);
- qs3 = reserves_update (pg,
- &updated_reserve);
- switch (qs3)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- return qs3;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return qs3;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* How can the UPDATE not work here? Very strange. */
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* continued below */
- break;
- }
- }
- notify_on_reserve (pg,
- reserve_pub);
- /* Go back to original transaction mode */
- {
- enum GNUNET_DB_QueryStatus cs;
-
- cs = postgres_commit (pg);
- if (cs < 0)
- return cs;
- if (GNUNET_OK !=
- postgres_start_read_committed (pg,
- "reserve-insert-continued"))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-}
-
-
-/**
- * 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 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,
- const struct TALER_BlindedCoinHash *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),
- 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),
- 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 (pg->conn,
- "lock_withdraw",
- no_params)))
- return qs;
-#endif
- collectable->h_coin_envelope = *h_blind;
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_withdraw_info",
- params,
- rs);
-}
-
-
-/**
- * Perform withdraw operation, checking for sufficient balance
- * and possibly persisting the withdrawal details.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param collectable corresponding collectable coin (blind signature)
- * if a coin is found
- * @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] kyc_ok set to true if the kyc status of the reserve is satisfied
- * @param[out] ruuid set to the reserve's UUID (reserves table row)
- * @return query execution status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_do_withdraw (
- void *cls,
- const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
- struct GNUNET_TIME_Timestamp now,
- bool *found,
- bool *balance_ok,
- struct TALER_EXCHANGEDB_KycStatus *kyc,
- uint64_t *ruuid)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Timestamp gc;
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_amount (&collectable->amount_with_fee),
- GNUNET_PQ_query_param_auto_from_type (&collectable->denom_pub_hash),
- GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_pub),
- 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_timestamp (&gc),
- 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),
- GNUNET_PQ_result_spec_bool ("kyc_ok",
- &kyc->ok),
- GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
- &kyc->payment_target_uuid),
- 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));
- kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "call_withdraw",
- params,
- rs);
-
-}
-
-
-/**
- * Check that reserve remains below threshold for KYC
- * checks after withdraw operation.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param ruuid reserve to check
- * @param withdraw_start starting point to accumulate from
- * @param upper_limit maximum amount allowed
- * @param[out] below_limit set to true if the limit was not exceeded
- * @return query execution status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_do_withdraw_limit_check (
- void *cls,
- uint64_t ruuid,
- struct GNUNET_TIME_Absolute withdraw_start,
- const struct TALER_Amount *upper_limit,
- bool *below_limit)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&ruuid),
- GNUNET_PQ_query_param_absolute_time (&withdraw_start),
- TALER_PQ_query_param_amount (upper_limit),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_bool ("below_limit",
- below_limit),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "call_withdraw_limit_check",
- params,
- rs);
-}
-
-
-/**
- * Compute the shard number of a given @a deposit
- *
- * @param deposit deposit to compute shard for
- * @return shard number
- */
-static uint64_t
-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;
-}
-
-
-/**
- * Perform deposit operation, checking for sufficient balance
- * of the coin and possibly persisting the deposit details.
- *
- * FIXME: parameters missing in description!
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param deposit deposit operation details
- * @param known_coin_id row of the coin in the known_coins table
- * @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] in_conflict set to true if the deposit conflicted
- * @return query execution status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_do_deposit (
- void *cls,
- const struct TALER_EXCHANGEDB_Deposit *deposit,
- uint64_t known_coin_id,
- const struct TALER_PaytoHash *h_payto,
- bool extension_blocked,
- struct GNUNET_TIME_Timestamp *exchange_timestamp,
- bool *balance_ok,
- bool *in_conflict)
-{
- struct PostgresClosure *pg = cls;
- uint64_t deposit_shard = compute_shard (&deposit->merchant_pub);
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_amount (&deposit->amount_with_fee),
- GNUNET_PQ_query_param_auto_from_type (&deposit->h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (&deposit->wire_salt),
- GNUNET_PQ_query_param_timestamp (&deposit->timestamp),
- GNUNET_PQ_query_param_timestamp (exchange_timestamp),
- GNUNET_PQ_query_param_timestamp (&deposit->refund_deadline),
- GNUNET_PQ_query_param_timestamp (&deposit->wire_deadline),
- GNUNET_PQ_query_param_auto_from_type (&deposit->merchant_pub),
- GNUNET_PQ_query_param_string (deposit->receiver_wire_account),
- GNUNET_PQ_query_param_auto_from_type (h_payto),
- GNUNET_PQ_query_param_uint64 (&known_coin_id),
- GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub),
- GNUNET_PQ_query_param_auto_from_type (&deposit->csig),
- GNUNET_PQ_query_param_uint64 (&deposit_shard),
- GNUNET_PQ_query_param_bool (extension_blocked),
- (NULL == deposit->extension_details)
- ? GNUNET_PQ_query_param_null ()
- : TALER_PQ_query_param_json (deposit->extension_details),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_bool ("balance_ok",
- balance_ok),
- GNUNET_PQ_result_spec_bool ("conflicted",
- in_conflict),
- GNUNET_PQ_result_spec_timestamp ("exchange_timestamp",
- exchange_timestamp),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "call_deposit",
- params,
- rs);
-}
-
-
-/**
- * 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[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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_do_melt (
- void *cls,
- 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[] = {
- TALER_PQ_query_param_amount (&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;
-
- 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;
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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 = compute_shard (&refund->details.merchant_pub);
- struct TALER_Amount amount_without_fee;
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_amount (&refund->details.refund_amount),
- TALER_PQ_query_param_amount (&amount_without_fee),
- TALER_PQ_query_param_amount (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;
- }
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "call_refund",
- params,
- rs);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_do_recoup (
- void *cls,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- uint64_t reserve_out_serial_id,
- const union TALER_DenominationBlindingKeyP *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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "call_recoup",
- params,
- rs);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_do_recoup_refresh (
- void *cls,
- const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
- uint64_t rrc_serial,
- const union TALER_DenominationBlindingKeyP *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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "call_recoup_refresh",
- params,
- rs);
-}
-
-
-/**
- * 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;
-
- /**
- * Sum of all credit transactions.
- */
- struct TALER_Amount balance_in;
-
- /**
- * Sum of all debit transactions.
- */
- struct TALER_Amount balance_out;
-
- /**
- * Set to #GNUNET_SYSERR on serious internal errors during
- * the callbacks.
- */
- enum GNUNET_GenericReturnValue 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_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->status = GNUNET_SYSERR;
- 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 #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),
- 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->status = GNUNET_SYSERR;
- 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 #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),
- 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->status = GNUNET_SYSERR;
- 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
- * #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),
- 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->status = GNUNET_SYSERR;
- 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)' */
-}
-
-
-/**
- * Get all of the transaction history associated with 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
- * @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,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- struct TALER_Amount *balance,
- 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;
- 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));
- 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 (pg->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;
- GNUNET_assert (0 <=
- TALER_amount_subtract (balance,
- &rhc.balance_in,
- &rhc.balance_out));
- return qs;
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_have_deposit2 (
- void *cls,
- const struct TALER_PrivateContractHash *h_contract_terms,
- const struct TALER_MerchantWireHash *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_MerchantWireHash h_wire2;
-#if EXPLICIT_LOCKS
- struct GNUNET_PQ_QueryParam no_params[] = {
- GNUNET_PQ_query_param_end
- };
-
- if (0 > (qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "lock_deposit",
- no_params)))
- return qs;
-#endif
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Getting deposits for coin %s\n",
- TALER_B2S (coin_pub));
- 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! (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!) FIXME #3838 */
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- }
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-}
-
-
-/**
- * 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 rowid identifies the deposit row to modify
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_mark_deposit_tiny (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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "mark_deposit_tiny",
- params);
-}
-
-
-/**
- * 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 rowid identifies the deposit row to modify
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_mark_deposit_done (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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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", 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 kyc_off true if we should not check the KYC status because
- * this exchange does not need/support KYC checks.
- * @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,
- uint64_t start_shard_row,
- uint64_t end_shard_row,
- bool kyc_off,
- TALER_EXCHANGEDB_DepositIterator deposit_cb,
- void *deposit_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_uint64 (&start_shard_row),
- GNUNET_PQ_query_param_uint64 (&end_shard_row),
- GNUNET_PQ_query_param_bool (kyc_off),
- GNUNET_PQ_query_param_end
- };
- struct TALER_Amount amount_with_fee;
- struct TALER_Amount deposit_fee;
- struct TALER_PrivateContractHash h_contract_terms;
- struct TALER_MerchantPublicKeyP merchant_pub;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- uint64_t serial_id;
- uint64_t wire_target;
- char *payto_uri;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
- &serial_id),
- GNUNET_PQ_result_spec_uint64 ("wire_target_serial_id",
- &wire_target),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- &deposit_fee),
- 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),
- GNUNET_PQ_result_spec_string ("payto_uri",
- &payto_uri),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- now = GNUNET_TIME_absolute_get ();
- GNUNET_assert (start_shard_row < end_shard_row);
- GNUNET_assert (end_shard_row <= INT32_MAX);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Finding ready deposits by deadline %s (%llu)\n",
- GNUNET_TIME_absolute2s (now),
- (unsigned long long) now.abs_value_us);
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->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_target,
- payto_uri);
- GNUNET_PQ_cleanup_result (rs);
- return qs;
-}
-
-
-/**
- * Closure for #match_deposit_cb().
- */
-struct MatchingDepositContext
-{
- /**
- * Function to call for each result
- */
- TALER_EXCHANGEDB_MatchingDepositIterator 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.
- */
- enum GNUNET_GenericReturnValue 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_INFO,
- "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 TALER_PrivateContractHash 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),
- 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,
- &coin_pub,
- &amount_with_fee,
- &deposit_fee,
- &h_contract_terms);
- 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 wire_target 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,
- uint64_t wire_target,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- TALER_EXCHANGEDB_MatchingDepositIterator deposit_cb,
- void *deposit_cb_cls,
- uint32_t 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 (merchant_pub),
- GNUNET_PQ_query_param_uint64 (&wire_target),
- GNUNET_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_end
- };
- struct MatchingDepositContext mdc = {
- .deposit_cb = deposit_cb,
- .deposit_cb_cls = deposit_cb_cls,
- .merchant_pub = merchant_pub,
- .pg = pg,
- .limit = limit,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->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 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,
- 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_auto_from_type ("age_hash",
- &coin_info->age_commitment_hash),
- 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;
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_known_coin",
- params,
- rs);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_coin_denomination (
- void *cls,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- uint64_t *known_coin_id,
- struct TALER_DenominationHash *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));
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_coin_denomination",
- params,
- rs);
-}
-
-
-/**
- * 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`
- */
-static long long
-postgres_count_known_coins (void *cls,
- const struct TALER_DenominationHash *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;
-
- 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;
-}
-
-
-/**
- * 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] age_hash set to the conflicting age hash on conflict
- * @return database transaction status, non-negative on success
- */
-static enum TALER_EXCHANGEDB_CoinKnownStatus
-postgres_ensure_coin_known (void *cls,
- const struct TALER_CoinPublicInfo *coin,
- uint64_t *known_coin_id,
- struct TALER_DenominationHash *denom_hash,
- struct TALER_AgeHash *age_hash)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus qs;
- bool existed;
- 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),
- GNUNET_PQ_query_param_auto_from_type (&coin->age_commitment_hash),
- TALER_PQ_query_param_denom_sig (&coin->denom_sig),
- GNUNET_PQ_query_param_end
- };
- bool is_null = false;
- 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 ("age_hash",
- age_hash),
- &is_null),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- denom_hash),
- &is_null),
- GNUNET_PQ_result_spec_end
- };
-
- GNUNET_break (GNUNET_is_zero (&coin->age_commitment_hash)); // FIXME-OEC
- 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_null) &&
- (0 != GNUNET_memcmp (age_hash,
- &coin->age_commitment_hash)) )
- {
- GNUNET_break (GNUNET_is_zero (age_hash)); // FIXME-OEC
- GNUNET_break_op (0);
- return TALER_EXCHANGEDB_CKS_AGE_CONFLICT;
- }
- if ( (! is_null) &&
- (0 != GNUNET_memcmp (denom_hash,
- &coin->denom_pub_hash)) )
- {
- GNUNET_break_op (0);
- return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT;
- }
- return TALER_EXCHANGEDB_CKS_PRESENT;
-}
-
-
-/**
- * Insert information about deposited coin into the database.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param exchange_timestamp time the exchange received the deposit request
- * @param deposit deposit information to store
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_deposit (void *cls,
- struct GNUNET_TIME_Timestamp exchange_timestamp,
- const struct TALER_EXCHANGEDB_Deposit *deposit)
-{
- struct PostgresClosure *pg = cls;
- struct TALER_EXCHANGEDB_KycStatus kyc;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = inselect_account_kyc_status (pg,
- deposit->receiver_wire_account,
- &kyc);
- if (qs <= 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
- {
- uint64_t shard = compute_shard (&deposit->merchant_pub);
- 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),
- GNUNET_PQ_query_param_timestamp (&deposit->timestamp),
- GNUNET_PQ_query_param_timestamp (&deposit->refund_deadline),
- GNUNET_PQ_query_param_timestamp (&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->wire_salt),
- GNUNET_PQ_query_param_uint64 (&kyc.payment_target_uuid),
- GNUNET_PQ_query_param_auto_from_type (&deposit->csig),
- GNUNET_PQ_query_param_timestamp (&exchange_timestamp),
- GNUNET_PQ_query_param_uint64 (&shard),
- GNUNET_PQ_query_param_end
- };
-
- GNUNET_assert (shard <= INT32_MAX);
- GNUNET_log (
- GNUNET_ERROR_TYPE_INFO,
- "Inserting deposit to be executed at %s (%llu/%llu)\n",
- GNUNET_TIME_timestamp2s (deposit->wire_deadline),
- (unsigned long long) deposit->wire_deadline.abs_time.abs_value_us,
- (unsigned long long) deposit->refund_deadline.abs_time.abs_value_us);
- return GNUNET_PQ_eval_prepared_non_select (pg->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 refund refund information to store
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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 (&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));
- return GNUNET_PQ_eval_prepared_non_select (pg->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 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,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_PrivateContractHash *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 (pg->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 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_melt (void *cls,
- const struct TALER_RefreshCommitmentP *rc,
- struct TALER_EXCHANGEDB_Melt *melt,
- uint64_t *melt_serial_id)
-{
- 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_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));
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_melt",
- params,
- rs);
- melt->session.rc = *rc;
- return qs;
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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;
- }
- 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),
- GNUNET_PQ_query_param_fixed_size (rrc->coin_ev,
- rrc->coin_ev_size),
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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++)
- {
- 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[] = {
- GNUNET_PQ_result_spec_auto_from_type ("h_denom_pub",
- &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),
- GNUNET_PQ_result_spec_variable_size ("coin_ev",
- (void **) &rrc->coin_ev,
- &rrc->coin_ev_size),
- TALER_PQ_result_spec_blinded_denom_sig ("ev_sig",
- &rrc->coin_sig),
- GNUNET_PQ_result_spec_end
- };
-
- if (NULL != rrc->coin_ev)
- {
- /* 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;
- }
- }
- }
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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));
- 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);
- GNUNET_free (rrc->coin_ev);
- }
- GNUNET_free (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;
- TALER_denom_pub_free (&ldl->denom_pub);
- TALER_blinded_denom_sig_free (&ldl->ev_sig);
- 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),
- TALER_PQ_result_spec_blinded_denom_sig ("ev_sig",
- &pos->ev_sig),
- TALER_PQ_result_spec_denom_pub ("denom_pub",
- &pos->denom_pub),
- 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 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,
- 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;
-
- ldctx.ldc = ldc;
- ldctx.ldc_cls = ldc_cls;
- ldctx.last = NULL;
- ldctx.status = GNUNET_OK;
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->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;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Set to 'true' if the transaction failed.
- */
- bool failed;
-
- /**
- * Set to 'true' if we found a deposit or melt (for invariant check).
- */
- bool have_deposit_or_melt;
-};
-
-
-/**
- * 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;
-
- chc->have_deposit_or_melt = true;
- 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_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 ("denom_pub_hash",
- &deposit->h_denom_pub),
- 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 ("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_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;
-
- chc->have_deposit_or_melt = true;
- 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_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_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;
- }
-}
-
-
-/**
- * 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 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,
- 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,
- .coin_pub = coin_pub,
- .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 (pg->conn,
- work[i].statement,
- params,
- work[i].cb,
- &chc);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Coin %s yielded %d transactions of type %s\n",
- TALER_B2S (coin_pub),
- qs,
- work[i].statement);
- if ( (0 > qs) ||
- (chc.failed) )
- {
- if (NULL != chc.head)
- common_free_coin_transaction_list (cls,
- chc.head);
- *tlp = NULL;
- if (chc.failed)
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- 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 TALER_PrivateContractHash h_contract_terms;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- 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),
- 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,
- 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 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,
- 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 (pg->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 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] 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)
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_transfer_by_deposit (
- void *cls,
- const struct TALER_PrivateContractHash *h_contract_terms,
- const struct TALER_MerchantWireHash *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)
-{
- 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_WireSalt 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
- };
-
- /* check if the aggregation record exists and get it */
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_deposit_wtid",
- params,
- rs);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- struct TALER_MerchantWireHash wh;
-
- TALER_merchant_wire_signature_hash (payto_uri,
- &wire_salt,
- &wh);
- GNUNET_PQ_cleanup_result (rs);
- if (0 ==
- GNUNET_memcmp (&wh,
- h_wire))
- {
- *pending = false;
- memset (kyc,
- 0,
- sizeof (*kyc));
- kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT;
- kyc->ok = true;
- return qs;
- }
- qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- }
- 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). */
- 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),
- GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
- &kyc->payment_target_uuid),
- GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
- &kyc->ok),
- 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
- };
-
- 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_MerchantWireHash wh;
-
- TALER_merchant_wire_signature_hash (payto_uri,
- &wire_salt,
- &wh);
- GNUNET_PQ_cleanup_result (rs);
- if (0 !=
- GNUNET_memcmp (&wh,
- h_wire))
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- }
- kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT;
- return qs;
- }
-}
-
-
-/**
- * Function called to insert aggregation information into the DB.
- *
- * @param cls closure
- * @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,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- unsigned long long deposit_serial_id)
-{
- struct PostgresClosure *pg = cls;
- 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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_aggregation_tracking",
- params);
-}
-
-
-/**
- * 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] 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,
- const char *type,
- struct GNUNET_TIME_Timestamp date,
- struct GNUNET_TIME_Timestamp *start_date,
- struct GNUNET_TIME_Timestamp *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),
- 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", 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 (pg->conn,
- "get_wire_fee",
- params,
- rs);
-}
-
-
-/**
- * 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 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,
- const char *type,
- struct GNUNET_TIME_Timestamp start_date,
- struct GNUNET_TIME_Timestamp 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),
- GNUNET_PQ_query_param_timestamp (&start_date),
- GNUNET_PQ_query_param_timestamp (&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_Timestamp sd;
- struct GNUNET_TIME_Timestamp ed;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = postgres_get_wire_fee (pg,
- 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 ( (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;
- }
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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;
-
- 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",
- &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 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 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;
- 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 (pg->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 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,
- 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)
-{
- struct PostgresClosure *pg = cls;
- struct TALER_EXCHANGEDB_Reserve reserve;
- struct TALER_EXCHANGEDB_KycStatus kyc;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = inselect_account_kyc_status (pg,
- receiver_account,
- &kyc);
- if (qs <= 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
- {
- 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_uint64 (&kyc.payment_target_uuid),
- TALER_PQ_query_param_amount (amount_with_fee),
- TALER_PQ_query_param_amount (closing_fee),
- GNUNET_PQ_query_param_end
- };
-
- 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 = reserves_get_internal (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 reserves_update (cls,
- &reserve);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "wire_prepare_data_insert",
- params);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "wire_prepare_data_mark_done",
- params);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "wire_prepare_data_mark_failed",
- params);
-}
-
-
-/**
- * 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);
- }
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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;
-
- 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;
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_GenericReturnValue
-postgres_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 wire_out_ref DEFERRED"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- if (GNUNET_SYSERR ==
- postgres_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);
- postgres_rollback (pg);
- return GNUNET_SYSERR;
- }
- pg->transaction_name = "deferred wire out";
- return GNUNET_OK;
-}
-
-
-/**
- * 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 wire_target 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_store_wire_transfer_out (
- void *cls,
- struct GNUNET_TIME_Timestamp date,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- uint64_t wire_target,
- 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_uint64 (&wire_target),
- GNUNET_PQ_query_param_string (exchange_account_section),
- TALER_PQ_query_param_amount (amount),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->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 enum GNUNET_GenericReturnValue
-postgres_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_PreparedStatement ps[] = {
- /* Used in #postgres_gc() */
- GNUNET_PQ_make_prepare ("run_gc",
- "CALL"
- " exchange_do_gc"
- " ($1,$2);",
- 2),
- 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,
- "run_gc",
- params))
- ret = GNUNET_SYSERR;
- 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 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_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 ("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,
- exchange_timestamp,
- &deposit,
- &denom_pub,
- 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 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,
- 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 (pg->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[] = {
- TALER_PQ_result_spec_denom_pub ("denom_pub",
- &denom_pub),
- 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 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,
- 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 (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;
-}
-
-
-/**
- * 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),
- 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
- };
- 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 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,
- 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 (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;
-}
-
-
-/**
- * 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_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
- };
- 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 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,
- 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 (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;
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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;
-
- 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;
-}
-
-
-/**
- * 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 TALER_BlindedCoinHash 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
- };
- 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 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,
- 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 (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;
-}
-
-
-/**
- * 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;
- }
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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;
-
- 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;
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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;
-
- 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;
-}
-
-
-/**
- * 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 TALER_DenominationBlindingKeyP coin_blind;
- struct TALER_Amount amount;
- struct TALER_DenominationPublicKey denom_pub;
- struct TALER_BlindedCoinHash 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),
- 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;
- }
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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;
-
- 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;
-}
-
-
-/**
- * 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;
- union TALER_DenominationBlindingKeyP coin_blind;
- struct TALER_DenominationPublicKey denom_pub;
- struct TALER_DenominationHash old_denom_pub_hash;
- struct TALER_Amount amount;
- struct TALER_BlindedCoinHash 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),
- 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,
- &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 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,
- 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 (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;
-}
-
-
-/**
- * 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_Timestamp 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),
- 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_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 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,
- 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 (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;
-}
-
-
-/**
- * Obtain information about which reserve a coin was generated
- * from given the hash of the blinded coin.
- *
- * @param cls closure
- * @param h_blind_ev hash of the blinded coin
- * @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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_reserve_by_h_blind (void *cls,
- const struct TALER_BlindedCoinHash *h_blind_ev,
- 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 (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_uint64 ("reserve_out_serial_id",
- reserve_out_serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->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 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,
- const struct TALER_BlindedCoinHash *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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "old_coin_by_h_blind",
- params,
- rs);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_denomination_revocation (
- void *cls,
- const struct TALER_DenominationHash *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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "denomination_revocation_insert",
- params);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_denomination_revocation (
- void *cls,
- const struct TALER_DenominationHash *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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->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;
- char *payto_uri;
- struct GNUNET_TIME_Timestamp deadline;
- bool tiny;
- bool 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),
- GNUNET_PQ_result_spec_string ("payto_uri",
- &payto_uri),
- GNUNET_PQ_result_spec_timestamp ("wire_deadline",
- &deadline),
- GNUNET_PQ_result_spec_bool ("tiny",
- &tiny),
- GNUNET_PQ_result_spec_bool ("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,
- payto_uri,
- 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 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 GNUNET_TIME_Timestamp start_date,
- struct GNUNET_TIME_Timestamp end_date,
- TALER_EXCHANGEDB_WireMissingCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_timestamp (&start_date),
- GNUNET_PQ_query_param_timestamp (&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 (pg->conn,
- "deposits_get_overdue",
- params,
- &missing_wire_cb,
- &mwc);
- if (GNUNET_OK != mwc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_auditor_timestamp",
- params,
- rs);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_auditor_status",
- params,
- rs);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_auditor",
- params);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_auditor",
- params);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_wire_timestamp",
- params,
- rs);
-}
-
-
-/**
- * Insert information about an wire account used by this exchange.
- *
- * @param cls closure
- * @param payto_uri wire account of the exchange
- * @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
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_wire (void *cls,
- const char *payto_uri,
- struct GNUNET_TIME_Timestamp start_date,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (payto_uri),
- GNUNET_PQ_query_param_auto_from_type (master_sig),
- GNUNET_PQ_query_param_timestamp (&start_date),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_wire",
- params);
-}
-
-
-/**
- * Update information about a wire account of the exchange.
- *
- * @param cls closure
- * @param payto_uri account the update is about
- * @param change_date date when the account status was last changed
- * (only to be used for replay detection)
- * @param enabled true to enable, false to disable (the actual change)
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_wire (void *cls,
- const char *payto_uri,
- struct GNUNET_TIME_Timestamp change_date,
- bool enabled)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (payto_uri),
- GNUNET_PQ_query_param_bool (enabled),
- GNUNET_PQ_query_param_timestamp (&change_date),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_wire",
- params);
-}
-
-
-/**
- * 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.
- */
- 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
-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;
- struct TALER_MasterSignatureP master_sig;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("payto_uri",
- &payto_uri),
- 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,
- payto_uri,
- &master_sig);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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;
-
- 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;
-
-}
-
-
-/**
- * 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.
- */
- 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
-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_Amount wire_fee;
- struct TALER_Amount closing_fee;
- struct GNUNET_TIME_Timestamp start_date;
- struct GNUNET_TIME_Timestamp end_date;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
- &wire_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
- &closing_fee),
- 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,
- &wire_fee,
- &closing_fee,
- start_date,
- end_date,
- &master_sig);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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;
-
- 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;
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_signkey_revocation",
- params);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_signkey_revocation",
- params,
- rs);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_denomination_key (
- void *cls,
- const struct TALER_DenominationHash *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->fee_withdraw),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- &meta->fee_deposit),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
- &meta->fee_refresh),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
- &meta->fee_refund),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_denomination_key",
- params,
- rs);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_add_denomination_key (
- void *cls,
- const struct TALER_DenominationHash *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 (&meta->value),
- TALER_PQ_query_param_amount (&meta->fee_withdraw),
- TALER_PQ_query_param_amount (&meta->fee_deposit),
- TALER_PQ_query_param_amount (&meta->fee_refresh),
- TALER_PQ_query_param_amount (&meta->fee_refund),
- GNUNET_PQ_query_param_end
- };
-
- /* Sanity check: ensure fees match coin currency */
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (&meta->value,
- &meta->fee_withdraw));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (&meta->value,
- &meta->fee_deposit));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (&meta->value,
- &meta->fee_refresh));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (&meta->value,
- &meta->fee_refund));
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "denomination_insert",
- iparams);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_signkey",
- iparams);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_signing_key",
- params,
- rs);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_auditor_denom_sig (
- void *cls,
- const struct TALER_DenominationHash *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
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_auditor_denom_sig",
- params);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_auditor_denom_sig (
- void *cls,
- const struct TALER_DenominationHash *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
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "select_auditor_denom_sig",
- params,
- rs);
-}
-
-
-/**
- * Closure for #wire_fee_by_time_helper()
- */
-struct WireFeeLookupContext
-{
-
- /**
- * Set to the wire fee. Set to invalid if fees conflict over
- * the given time period.
- */
- struct TALER_Amount *wire_fee;
-
- /**
- * Set to the closing fee. Set to invalid if fees conflict over
- * the given time period.
- */
- struct TALER_Amount *closing_fee;
-
- /**
- * 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
-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_Amount wf;
- struct TALER_Amount cf;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
- &wf),
- TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
- &cf),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- /* invalidate */
- memset (wlc->wire_fee,
- 0,
- sizeof (struct TALER_Amount));
- memset (wlc->closing_fee,
- 0,
- sizeof (struct TALER_Amount));
- return;
- }
- if (0 == i)
- {
- *wlc->wire_fee = wf;
- *wlc->closing_fee = cf;
- continue;
- }
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&wf,
- wlc->wire_fee)) ||
- (GNUNET_YES !=
- TALER_amount_cmp_currency (&cf,
- wlc->closing_fee)) ||
- (0 !=
- TALER_amount_cmp (&wf,
- wlc->wire_fee)) ||
- (0 !=
- TALER_amount_cmp (&cf,
- wlc->closing_fee)) )
- {
- /* invalidate */
- memset (wlc->wire_fee,
- 0,
- sizeof (struct TALER_Amount));
- memset (wlc->closing_fee,
- 0,
- sizeof (struct TALER_Amount));
- return;
- }
- }
-}
-
-
-/**
- * Lookup information about known wire fees. Finds all applicable
- * fees in the given range. If they are identical, returns the
- * respective @a wire_fee and @a closing_fee. 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] wire_fee wire fee for that time period; if
- * different wire fee exists within this time
- * period, an 'invalid' amount is returned.
- * @param[out] closing_fee wire fee for that time period; if
- * different wire fee exists within this time
- * period, an 'invalid' amount is returned.
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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_Amount *wire_fee,
- struct TALER_Amount *closing_fee)
-{
- 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 = {
- .wire_fee = wire_fee,
- .closing_fee = closing_fee,
- .pg = pg,
- };
-
- return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_wire_fee_by_time",
- params,
- &wire_fee_by_time_helper,
- &wlc);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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;
-
- switch (table)
- {
- case TALER_EXCHANGEDB_RT_DENOMINATIONS:
- statement = "select_serial_by_table_denominations";
- break;
- case TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS:
- statement = "select_serial_by_table_denomination_revocations";
- break;
- case TALER_EXCHANGEDB_RT_RESERVES:
- statement = "select_serial_by_table_reserves";
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_IN:
- statement = "select_serial_by_table_reserves_in";
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_CLOSE:
- statement = "select_serial_by_table_reserves_close";
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_OUT:
- statement = "select_serial_by_table_reserves_out";
- break;
- case TALER_EXCHANGEDB_RT_AUDITORS:
- statement = "select_serial_by_table_auditors";
- break;
- case TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS:
- statement = "select_serial_by_table_auditor_denom_sigs";
- break;
- case TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS:
- statement = "select_serial_by_table_exchange_sign_keys";
- break;
- case TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS:
- statement = "select_serial_by_table_signkey_revocations";
- break;
- case TALER_EXCHANGEDB_RT_KNOWN_COINS:
- statement = "select_serial_by_table_known_coins";
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS:
- statement = "select_serial_by_table_refresh_commitments";
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS:
- statement = "select_serial_by_table_refresh_revealed_coins";
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS:
- statement = "select_serial_by_table_refresh_transfer_keys";
- break;
- case TALER_EXCHANGEDB_RT_DEPOSITS:
- statement = "select_serial_by_table_deposits";
- break;
- case TALER_EXCHANGEDB_RT_REFUNDS:
- statement = "select_serial_by_table_refunds";
- break;
- case TALER_EXCHANGEDB_RT_WIRE_OUT:
- statement = "select_serial_by_table_wire_out";
- break;
- case TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING:
- statement = "select_serial_by_table_aggregation_tracking";
- break;
- case TALER_EXCHANGEDB_RT_WIRE_FEE:
- statement = "select_serial_by_table_wire_fee";
- break;
- case TALER_EXCHANGEDB_RT_RECOUP:
- statement = "select_serial_by_table_recoup";
- break;
- case TALER_EXCHANGEDB_RT_RECOUP_REFRESH:
- statement = "select_serial_by_table_recoup_refresh";
- break;
- default:
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- statement,
- params,
- rs);
-}
-
-
-/**
- * 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;
-};
-
-
-#include "lrbt_callbacks.c"
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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;
- const char *statement;
- enum GNUNET_DB_QueryStatus qs;
-
- switch (table)
- {
- case TALER_EXCHANGEDB_RT_DENOMINATIONS:
- statement = "select_above_serial_by_table_denominations";
- rh = &lrbt_cb_table_denominations;
- break;
- case TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS:
- statement = "select_above_serial_by_table_denomination_revocations";
- rh = &lrbt_cb_table_denomination_revocations;
- break;
- case TALER_EXCHANGEDB_RT_WIRE_TARGETS:
- statement = "select_above_serial_by_table_wire_targets";
- rh = &lrbt_cb_table_wire_targets;
- break;
- case TALER_EXCHANGEDB_RT_RESERVES:
- statement = "select_above_serial_by_table_reserves";
- rh = &lrbt_cb_table_reserves;
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_IN:
- statement = "select_above_serial_by_table_reserves_in";
- rh = &lrbt_cb_table_reserves_in;
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_CLOSE:
- statement = "select_above_serial_by_table_reserves_close";
- rh = &lrbt_cb_table_reserves_close;
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_OUT:
- statement = "select_above_serial_by_table_reserves_out";
- rh = &lrbt_cb_table_reserves_out;
- break;
- case TALER_EXCHANGEDB_RT_AUDITORS:
- statement = "select_above_serial_by_table_auditors";
- rh = &lrbt_cb_table_auditors;
- break;
- case TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS:
- statement = "select_above_serial_by_table_auditor_denom_sigs";
- rh = &lrbt_cb_table_auditor_denom_sigs;
- break;
- case TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS:
- statement = "select_above_serial_by_table_exchange_sign_keys";
- rh = &lrbt_cb_table_exchange_sign_keys;
- break;
- case TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS:
- statement = "select_above_serial_by_table_signkey_revocations";
- rh = &lrbt_cb_table_signkey_revocations;
- break;
- case TALER_EXCHANGEDB_RT_KNOWN_COINS:
- statement = "select_above_serial_by_table_known_coins";
- rh = &lrbt_cb_table_known_coins;
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS:
- statement = "select_above_serial_by_table_refresh_commitments";
- rh = &lrbt_cb_table_refresh_commitments;
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS:
- statement = "select_above_serial_by_table_refresh_revealed_coins";
- rh = &lrbt_cb_table_refresh_revealed_coins;
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS:
- statement = "select_above_serial_by_table_refresh_transfer_keys";
- rh = &lrbt_cb_table_refresh_transfer_keys;
- break;
- case TALER_EXCHANGEDB_RT_DEPOSITS:
- statement = "select_above_serial_by_table_deposits";
- rh = &lrbt_cb_table_deposits;
- break;
- case TALER_EXCHANGEDB_RT_REFUNDS:
- statement = "select_above_serial_by_table_refunds";
- rh = &lrbt_cb_table_refunds;
- break;
- case TALER_EXCHANGEDB_RT_WIRE_OUT:
- statement = "select_above_serial_by_table_wire_out";
- rh = &lrbt_cb_table_wire_out;
- break;
- case TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING:
- statement = "select_above_serial_by_table_aggregation_tracking";
- rh = &lrbt_cb_table_aggregation_tracking;
- break;
- case TALER_EXCHANGEDB_RT_WIRE_FEE:
- statement = "select_above_serial_by_table_wire_fee";
- rh = &lrbt_cb_table_wire_fee;
- break;
- case TALER_EXCHANGEDB_RT_RECOUP:
- statement = "select_above_serial_by_table_recoup";
- rh = &lrbt_cb_table_recoup;
- break;
- case TALER_EXCHANGEDB_RT_RECOUP_REFRESH:
- statement = "select_above_serial_by_table_recoup_refresh";
- rh = &lrbt_cb_table_recoup_refresh;
- break;
- default:
- 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)
- return qs;
- if (ctx.error)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Signature of helper functions of #postgres_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);
-
-
-#include "irbt_callbacks.c"
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_records_by_table (void *cls,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct PostgresClosure *pg = cls;
- InsertRecordCallback rh;
-
- 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_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_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_DEPOSITS:
- rh = &irbt_cb_table_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_RECOUP:
- rh = &irbt_cb_table_recoup;
- break;
- case TALER_EXCHANGEDB_RT_RECOUP_REFRESH:
- rh = &irbt_cb_table_recoup_refresh;
- break;
- default:
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- return rh (pg,
- 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 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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<3; retries++)
- {
- if (GNUNET_OK !=
- postgres_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_subtract (GNUNET_TIME_absolute_get (),
- delay);
- 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);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_rollback (pg);
- continue;
- 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_uint64 (start_row),
- GNUNET_PQ_query_param_uint64 (end_row),
- GNUNET_PQ_query_param_end
- };
-
- now = GNUNET_TIME_timestamp_get ();
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "reclaim_shard",
- params);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_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 */
- postgres_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
- };
-
- 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);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_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_Timestamp now;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (job_name),
- GNUNET_PQ_query_param_timestamp (&now),
- GNUNET_PQ_query_param_uint64 (start_row),
- GNUNET_PQ_query_param_uint64 (end_row),
- GNUNET_PQ_query_param_end
- };
-
- now = GNUNET_TIME_timestamp_get ();
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Trying to claim shard %llu-%llu\n",
- (unsigned long long) *start_row,
- (unsigned long long) *end_row);
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "claim_next_shard",
- params);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_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 */
- postgres_rollback (pg);
- continue;
- }
- } /* claim_next_shard */
-
- /* commit */
-commit:
- {
- enum GNUNET_DB_QueryStatus qs;
-
- qs = postgres_commit (pg);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_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;
-}
-
-
-/**
- * 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
-postgres_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);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "complete_shard",
- params);
-}
-
-
-/**
- * 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
- */
-static enum GNUNET_DB_QueryStatus
-postgres_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 !=
- postgres_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
- };
-
- 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);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_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_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
- };
-
- *end_row = GNUNET_MIN (shard_limit,
- *start_row + shard_size - 1);
- now = GNUNET_TIME_timestamp_get ();
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Trying to claim shard %llu-%llu\n",
- (unsigned long long) *start_row,
- (unsigned long long) *end_row);
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "create_revolving_shard",
- params);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_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 */
- postgres_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
- };
-
- 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);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_rollback (pg);
- continue;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* no open shards available */
- postgres_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 ();
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "reclaim_revolving_shard",
- params);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_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 */
- postgres_rollback (pg);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- break; /* continue with commit */
- }
- } /* end claim oldest existing shard */
-
- /* commit */
- {
- enum GNUNET_DB_QueryStatus qs;
-
- qs = postgres_commit (pg);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_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;
-}
-
-
-/**
- * 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
-postgres_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);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "release_revolving_shard",
- params);
-}
-
-
-/**
- * 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_DB_QueryStatus
-postgres_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);
-}
-
-
-/**
- * Function called to save the configuration of an extension
- * (age-restriction, peer2peer, ...). After succesfull 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 config JSON object of the configuration as string
- * @param config_sig signature of the configuration by the offline master key
- * @return transaction status code
- */
-enum GNUNET_DB_QueryStatus
-postgres_set_extension_config (void *cls,
- const char *extension_name,
- const char *config,
- const struct TALER_MasterSignatureP *config_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (extension_name),
- GNUNET_PQ_query_param_string (config),
- GNUNET_PQ_query_param_auto_from_type (config_sig),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "set_extension_config",
- params);
-}
-
-
-/**
- * Function called to get the configuration of an extension
- * (age-restriction, peer2peer, ...)
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param extension_name the name of the extension
- * @param[out] config JSON object of the configuration as string
- * @return transaction status code
- */
-enum GNUNET_DB_QueryStatus
-postgres_get_extension_config (void *cls,
- const char *extension_name,
- char **config)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (extension_name),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("config", config),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_extension_config",
- params,
- rs);
-}
-
-
-/**
* Initialize Postgres database subsystem.
*
* @param cls a configuration instance
@@ -11478,6 +321,7 @@ 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;
@@ -11489,7 +333,7 @@ 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;
}
@@ -11506,27 +350,61 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
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))
{
@@ -11536,8 +414,7 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
return NULL;
}
if (GNUNET_OK !=
- internal_setup (pg,
- true))
+ TEH_PG_internal_setup (pg))
{
GNUNET_free (pg->exchange_url);
GNUNET_free (pg->currency);
@@ -11545,164 +422,378 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
GNUNET_free (pg);
return NULL;
}
-
plugin = GNUNET_new (struct TALER_EXCHANGEDB_Plugin);
plugin->cls = pg;
- plugin->drop_tables = &postgres_drop_tables;
- plugin->create_tables = &postgres_create_tables;
- plugin->start = &postgres_start;
- plugin->start_read_committed = &postgres_start_read_committed;
- plugin->commit = &postgres_commit;
- plugin->preflight = &postgres_preflight;
- plugin->rollback = &postgres_rollback;
- plugin->event_listen = &postgres_event_listen;
- plugin->event_listen_cancel = &postgres_event_listen_cancel;
- plugin->event_notify = &postgres_event_notify;
- 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->iterate_denominations = &postgres_iterate_denominations;
- plugin->iterate_active_signkeys = &postgres_iterate_active_signkeys;
- plugin->iterate_active_auditors = &postgres_iterate_active_auditors;
- plugin->iterate_auditor_denominations =
- &postgres_iterate_auditor_denominations;
- plugin->reserves_get = &postgres_reserves_get;
- plugin->set_kyc_ok = &postgres_set_kyc_ok;
- plugin->select_kyc_status = &postgres_select_kyc_status;
- plugin->inselect_wallet_kyc_status = &postgres_inselect_wallet_kyc_status;
- plugin->reserves_in_insert = &postgres_reserves_in_insert;
- plugin->get_withdraw_info = &postgres_get_withdraw_info;
- plugin->do_withdraw = &postgres_do_withdraw;
- plugin->do_withdraw_limit_check = &postgres_do_withdraw_limit_check;
- plugin->do_deposit = &postgres_do_deposit;
- plugin->do_melt = &postgres_do_melt;
- plugin->do_refund = &postgres_do_refund;
- plugin->do_recoup = &postgres_do_recoup;
- plugin->do_recoup_refresh = &postgres_do_recoup_refresh;
- 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_deposit2 = &postgres_have_deposit2;
- plugin->mark_deposit_tiny = &postgres_mark_deposit_tiny;
- 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->get_melt = &postgres_get_melt;
- 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_mark_failed =
- &postgres_wire_prepare_data_mark_failed;
- 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;
+ = &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
- = &postgres_lookup_auditor_timestamp;
+ = &TEH_PG_lookup_auditor_timestamp;
plugin->lookup_auditor_status
- = &postgres_lookup_auditor_status;
+ = &TEH_PG_lookup_auditor_status;
plugin->insert_auditor
- = &postgres_insert_auditor;
- plugin->update_auditor
- = &postgres_update_auditor;
+ = &TEH_PG_insert_auditor;
plugin->lookup_wire_timestamp
- = &postgres_lookup_wire_timestamp;
+ = &TEH_PG_lookup_wire_timestamp;
plugin->insert_wire
- = &postgres_insert_wire;
+ = &TEH_PG_insert_wire;
plugin->update_wire
- = &postgres_update_wire;
+ = &TEH_PG_update_wire;
plugin->get_wire_accounts
- = &postgres_get_wire_accounts;
+ = &TEH_PG_get_wire_accounts;
plugin->get_wire_fees
- = &postgres_get_wire_fees;
+ = &TEH_PG_get_wire_fees;
plugin->insert_signkey_revocation
- = &postgres_insert_signkey_revocation;
+ = &TEH_PG_insert_signkey_revocation;
plugin->lookup_signkey_revocation
- = &postgres_lookup_signkey_revocation;
+ = &TEH_PG_lookup_signkey_revocation;
plugin->lookup_denomination_key
- = &postgres_lookup_denomination_key;
+ = &TEH_PG_lookup_denomination_key;
plugin->insert_auditor_denom_sig
- = &postgres_insert_auditor_denom_sig;
+ = &TEH_PG_insert_auditor_denom_sig;
plugin->select_auditor_denom_sig
- = &postgres_select_auditor_denom_sig;
- plugin->lookup_wire_fee_by_time
- = &postgres_lookup_wire_fee_by_time;
+ = &TEH_PG_select_auditor_denom_sig;
plugin->add_denomination_key
- = &postgres_add_denomination_key;
- plugin->activate_signing_key
- = &postgres_activate_signing_key;
+ = &TEH_PG_add_denomination_key;
plugin->lookup_signing_key
- = &postgres_lookup_signing_key;
- plugin->lookup_serial_by_table
- = &postgres_lookup_serial_by_table;
- plugin->lookup_records_by_table
- = &postgres_lookup_records_by_table;
- plugin->insert_records_by_table
- = &postgres_insert_records_by_table;
+ = &TEH_PG_lookup_signing_key;
plugin->begin_shard
- = &postgres_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
- = &postgres_complete_shard;
- plugin->begin_revolving_shard
- = &postgres_begin_revolving_shard;
+ = &TEH_PG_complete_shard;
plugin->release_revolving_shard
- = &postgres_release_revolving_shard;
+ = &TEH_PG_release_revolving_shard;
plugin->delete_shard_locks
- = &postgres_delete_shard_locks;
- plugin->set_extension_config
- = &postgres_set_extension_config;
- plugin->get_extension_config
- = &postgres_get_extension_config;
+ = &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;
}
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 e582025b1..7f0332686 100644
--- a/src/exchangedb/test-exchange-db-postgres.conf
+++ b/src/exchangedb/test-exchange-db-postgres.conf
@@ -28,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 6724e7b42..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-2021 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.
@@ -109,6 +110,64 @@ mark_prepare_cb (void *cls,
/**
+ * 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.
*
* @return #GNUNET_OK on success
@@ -162,18 +221,15 @@ check_reserve (const struct TALER_ReservePublicKeyP *pub,
const char *currency)
{
struct TALER_EXCHANGEDB_Reserve reserve;
- struct TALER_EXCHANGEDB_KycStatus kyc;
reserve.pub = *pub;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->reserves_get (plugin->cls,
- &reserve,
- &kyc));
+ &reserve));
FAILIF (value != reserve.balance.value);
FAILIF (fraction != reserve.balance.fraction);
FAILIF (0 != strcmp (currency,
reserve.balance.currency));
- FAILIF (kyc.ok);
return GNUNET_OK;
drop:
return GNUNET_SYSERR;
@@ -206,30 +262,24 @@ destroy_denom_key_pair (struct DenomKeyPair *dkp)
*
* @param size the size of the denomination key
* @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 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);
GNUNET_assert (GNUNET_OK ==
TALER_denom_priv_create (&dkp->priv,
&dkp->pub,
- TALER_DENOMINATION_RSA,
+ GNUNET_CRYPTO_BSA_RSA,
size));
/* Using memset() as fields like master key and signature
are not properly initialized for this test. */
@@ -237,39 +287,28 @@ create_denom_key_pair (unsigned int size,
0,
sizeof (struct TALER_EXCHANGEDB_DenominationKey));
dki.denom_pub = dkp->pub;
- dki.issue.properties.start = GNUNET_TIME_timestamp_hton (now);
- dki.issue.properties.expire_withdraw
- = GNUNET_TIME_timestamp_hton
- (GNUNET_TIME_absolute_to_timestamp
- (GNUNET_TIME_absolute_add (
- now.abs_time,
- GNUNET_TIME_UNIT_HOURS)));
- dki.issue.properties.expire_deposit
- = GNUNET_TIME_timestamp_hton (
- GNUNET_TIME_absolute_to_timestamp
- (GNUNET_TIME_absolute_add
- (now.abs_time,
- GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_HOURS, 2))));
- dki.issue.properties.expire_legal
- = GNUNET_TIME_timestamp_hton (
- GNUNET_TIME_absolute_to_timestamp
- (GNUNET_TIME_absolute_add
- (now.abs_time,
- 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);
+ 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.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.denom_hash);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->insert_denomination_info (plugin->cls,
&dki.denom_pub,
@@ -280,10 +319,9 @@ create_denom_key_pair (unsigned int size,
return NULL;
}
memset (&issue2, 0, sizeof (issue2));
- plugin->commit (plugin->cls);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->get_denomination_info (plugin->cls,
- &dki.issue.properties.denom_hash,
+ &dki.issue.denom_hash,
&issue2))
{
GNUNET_break (0);
@@ -302,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;
@@ -376,10 +411,9 @@ 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_memcmp (&acoin->h_denom_pub,
&bcoin->h_denom_pub));
@@ -402,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
@@ -411,14 +446,16 @@ static unsigned int auditor_row_cnt;
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
static enum GNUNET_GenericReturnValue
-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)
+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;
@@ -428,6 +465,7 @@ audit_refresh_session_cb (void *cls,
(void) amount_with_fee;
(void) noreveal_index;
(void) rc;
+ (void) h_age_commitment;
auditor_row_cnt++;
return GNUNET_OK;
}
@@ -474,7 +512,7 @@ handle_link_data_cb (void *cls,
break;
}
}
- GNUNET_assert (found);
+ GNUNET_assert (GNUNET_NO != found);
}
}
@@ -487,8 +525,9 @@ cb_wt_never (void *cls,
uint64_t serial_id,
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_PrivateContractHash *h_contract_terms,
+ 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,
@@ -498,6 +537,7 @@ cb_wt_never (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;
@@ -509,8 +549,8 @@ cb_wt_never (void *cls,
static struct TALER_MerchantPublicKeyP merchant_pub_wt;
-static struct TALER_MerchantWireHash h_wire_wt;
-static struct TALER_PrivateContractHash 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;
@@ -527,8 +567,9 @@ cb_wt_check (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_PrivateContractHash *h_contract_terms,
+ 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,
@@ -536,6 +577,7 @@ cb_wt_check (void *cls,
{
(void) rowid;
(void) denom_pub;
+ (void) h_payto;
GNUNET_assert (cls == &cb_wt_never);
GNUNET_assert (0 == GNUNET_memcmp (merchant_pub,
&merchant_pub_wt));
@@ -556,111 +598,13 @@ cb_wt_check (void *cls,
/**
- * Here #deposit_cb() will store the row ID of the deposit.
- */
-static uint64_t deposit_rowid;
-
-/**
- * Here #deposit_cb() will store the row ID of the account.
- */
-static uint64_t wire_target_row;
-
-
-/**
- * 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_target unique ID of the receiver account
- * @param payto_uri how to pay the merchant, URI in payto://-format;
- * @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 TALER_PrivateContractHash *h_contract_terms,
- uint64_t wire_target,
- const char *payto_uri)
-{
- struct TALER_EXCHANGEDB_Deposit *deposit = cls;
-
- 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 == GNUNET_memcmp (coin_pub,
- &deposit->coin.coin_pub)) &&
- (0 == strcmp (payto_uri,
- deposit->receiver_wire_account)) )
- {
- deposit_rowid = rowid;
- wire_target_row = wire_target;
- result = 9;
- }
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-}
-
-
-/**
- * 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 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
- * @return transaction status code, #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT to continue to iterate
+ * Here we store the hash of the payto URI.
*/
-static enum GNUNET_DB_QueryStatus
-matching_deposit_cb (void *cls,
- uint64_t rowid,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct TALER_PrivateContractHash *h_contract_terms)
-{
- struct TALER_EXCHANGEDB_Deposit *deposit = cls;
-
- deposit_rowid = rowid;
- if ( (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 != GNUNET_memcmp (coin_pub,
- &deposit->coin.coin_pub)) )
- {
- 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
@@ -702,6 +646,7 @@ 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
*/
@@ -712,8 +657,9 @@ audit_refund_cb (void *cls,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_MerchantSignatureP *merchant_sig,
- const struct TALER_PrivateContractHash *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;
@@ -725,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;
}
@@ -779,7 +726,7 @@ audit_reserve_in_cb (void *cls,
static enum GNUNET_GenericReturnValue
audit_reserve_out_cb (void *cls,
uint64_t rowid,
- const struct TALER_BlindedCoinHash *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,
@@ -810,8 +757,8 @@ test_gc (void)
struct DenomKeyPair *dkp;
struct GNUNET_TIME_Timestamp now;
struct GNUNET_TIME_Timestamp past;
- struct TALER_EXCHANGEDB_DenominationKeyInformationP issue2;
- struct TALER_DenominationHash denom_hash;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue2;
+ struct TALER_DenominationHashP denom_hash;
now = GNUNET_TIME_timestamp_get ();
past = GNUNET_TIME_absolute_to_timestamp (
@@ -822,10 +769,7 @@ test_gc (void)
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))
@@ -861,23 +805,21 @@ test_wire_fees (void)
{
struct GNUNET_TIME_Timestamp start_date;
struct GNUNET_TIME_Timestamp end_date;
- struct TALER_Amount wire_fee;
- struct TALER_Amount closing_fee;
+ struct TALER_WireFeeSet fees;
struct TALER_MasterSignatureP master_sig;
struct GNUNET_TIME_Timestamp sd;
struct GNUNET_TIME_Timestamp ed;
- struct TALER_Amount fee;
- struct TALER_Amount fee2;
+ struct TALER_WireFeeSet fees2;
struct TALER_MasterSignatureP ms;
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));
@@ -886,8 +828,7 @@ test_wire_fees (void)
"wire-method",
start_date,
end_date,
- &wire_fee,
- &closing_fee,
+ &fees,
&master_sig))
{
GNUNET_break (0);
@@ -898,8 +839,7 @@ test_wire_fees (void)
"wire-method",
start_date,
end_date,
- &wire_fee,
- &closing_fee,
+ &fees,
&master_sig))
{
GNUNET_break (0);
@@ -913,8 +853,7 @@ test_wire_fees (void)
end_date,
&sd,
&ed,
- &fee,
- &fee2,
+ &fees2,
&ms))
{
GNUNET_break (0);
@@ -926,8 +865,7 @@ test_wire_fees (void)
start_date,
&sd,
&ed,
- &fee,
- &fee2,
+ &fees2,
&ms))
{
GNUNET_break (0);
@@ -939,10 +877,8 @@ test_wire_fees (void)
(GNUNET_TIME_timestamp_cmp (ed,
!=,
end_date)) ||
- (0 != TALER_amount_cmp (&fee,
- &wire_fee)) ||
- (0 != TALER_amount_cmp (&fee2,
- &closing_fee)) ||
+ (0 != TALER_wire_fee_set_cmp (&fees,
+ &fees2)) ||
(0 != GNUNET_memcmp (&ms,
&master_sig)) )
{
@@ -995,14 +931,21 @@ audit_wire_cb (void *cls,
/**
* Test API relating to wire_out handling.
*
+ * @param bd batch deposit to test
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
-test_wire_out (const struct TALER_EXCHANGEDB_Deposit *deposit)
+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_timestamp_get ();
GNUNET_assert (GNUNET_OK ==
@@ -1016,12 +959,12 @@ test_wire_out (const struct TALER_EXCHANGEDB_Deposit *deposit)
plugin->start_deferred_wire_out (plugin->cls));
/* setup values for wire transfer aggregation data */
- merchant_pub_wt = deposit->merchant_pub;
- 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;
+ coin_fee_wt = fees.deposit;
GNUNET_assert (0 <
TALER_amount_subtract (&transfer_value_wt,
&coin_value_wt,
@@ -1033,13 +976,15 @@ test_wire_out (const struct TALER_EXCHANGEDB_Deposit *deposit)
NULL));
{
- struct TALER_PrivateContractHash h_contract_terms_wt2 = h_contract_terms_wt;
+ 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 !=
@@ -1053,32 +998,20 @@ test_wire_out (const struct TALER_EXCHANGEDB_Deposit *deposit)
&execution_time2,
&coin_contribution2,
&coin_fee2,
- &kyc));
+ &kyc,
+ &aml));
}
- /* insert WT data */
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_aggregation_tracking (plugin->cls,
- &wire_out_wtid,
- deposit_rowid));
-
- /* Now let's fix the transient constraint violation by
- putting in the WTID into the wire_out table */
{
struct TALER_ReservePublicKeyP rpub;
- struct TALER_EXCHANGEDB_KycStatus kyc;
memset (&rpub,
44,
sizeof (rpub));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->inselect_wallet_kyc_status (plugin->cls,
- &rpub,
- &kyc));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->store_wire_transfer_out (plugin->cls,
wire_out_date,
&wire_out_wtid,
- kyc.payment_target_uuid,
+ &h_payto,
"my-config-section",
&wire_out_amount));
}
@@ -1098,6 +1031,7 @@ test_wire_out (const struct TALER_EXCHANGEDB_Deposit *deposit)
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,
@@ -1110,7 +1044,9 @@ test_wire_out (const struct TALER_EXCHANGEDB_Deposit *deposit)
&execution_time2,
&coin_contribution2,
&coin_fee2,
- &kyc));
+ &kyc,
+ &aml));
+ FAILIF (TALER_AML_NORMAL != aml);
GNUNET_assert (0 == GNUNET_memcmp (&wtid2,
&wire_out_wtid));
GNUNET_assert (GNUNET_TIME_timestamp_cmp (execution_time2,
@@ -1157,9 +1093,9 @@ recoup_cb (void *cls,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const union TALER_DenominationBlindingKeyP *coin_blind)
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_blind)
{
- const union TALER_DenominationBlindingKeyP *cb = cls;
+ const union GNUNET_CRYPTO_BlindingSecretP *cb = cls;
(void) rowid;
(void) timestamp;
@@ -1178,55 +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 payto_uri 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 char *payto_uri,
- struct GNUNET_TIME_Timestamp deadline,
- bool tiny,
- bool 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;
+ const struct TALER_EXCHANGEDB_CoinDepositInformation *deposit = cls;
- (void) payto_uri;
+ (void) batch_deposit_serial_id;
(void) deadline;
- (void) rowid;
- if (tiny)
- {
- GNUNET_break (0);
- result = 66;
- }
- if (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;
- }
+ (void) wire_target_h_payto;
+ if (0 ==
+ TALER_amount_cmp (total_amount,
+ &deposit->amount_with_fee))
+ result = 8;
}
@@ -1265,7 +1178,7 @@ run (void *cls)
struct GNUNET_CONFIGURATION_Handle *cfg = cls;
struct TALER_CoinSpendSignatureP coin_sig;
struct GNUNET_TIME_Timestamp deadline;
- union TALER_DenominationBlindingKeyP coin_blind;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
struct TALER_ReservePublicKeyP reserve_pub;
struct TALER_ReservePublicKeyP reserve_pub2;
struct TALER_ReservePublicKeyP reserve_pub3;
@@ -1277,31 +1190,42 @@ run (void *cls)
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;
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;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_TIME_Timestamp now;
- struct TALER_WireSalt salt;
- union TALER_DenominationBlindingKeyP bks;
- struct TALER_CoinPubHash c_hash;
+ 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 ();
memset (&deposit,
0,
sizeof (deposit));
- deposit.receiver_wire_account = (char *) rcvr;
+ memset (&bd,
+ 0,
+ sizeof (bd));
+ bd.receiver_wire_account = (char *) rcvr;
+ bd.cdis = &deposit;
+ bd.num_cdis = 1;
memset (&salt,
45,
sizeof (salt));
@@ -1318,10 +1242,12 @@ run (void *cls)
}
(void) plugin->drop_tables (plugin->cls);
if (GNUNET_OK !=
- plugin->create_tables (plugin->cls))
+ plugin->create_tables (plugin->cls,
+ true,
+ num_partitions))
{
result = 77;
- goto drop;
+ goto cleanup;
}
plugin->preflight (plugin->cls);
FAILIF (GNUNET_OK !=
@@ -1334,37 +1260,52 @@ run (void *cls)
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));
- deposit.deposit_fee = 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->commit (plugin->cls));
now = GNUNET_TIME_timestamp_get ();
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->reserves_in_insert (plugin->cls,
- &reserve_pub,
- &value,
- now,
- sndr,
- "exchange-account-1",
- 4));
+ {
+ 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 (&reserve_pub,
value.value,
@@ -1372,14 +1313,28 @@ run (void *cls)
value.currency));
now = GNUNET_TIME_timestamp_get ();
RND_BLK (&reserve_pub2);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->reserves_in_insert (plugin->cls,
- &reserve_pub2,
- &value,
- now,
- sndr,
- "exchange-account-1",
- 5));
+ {
+ 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 !=
+ plugin->start (plugin->cls,
+ "test-2"));
FAILIF (GNUNET_OK !=
check_reserve (&reserve_pub,
value.value,
@@ -1395,61 +1350,101 @@ run (void *cls)
dkp = create_denom_key_pair (RSA_KEY_SIZE,
now,
&value,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund);
+ &fees);
GNUNET_assert (NULL != dkp);
TALER_denom_pub_hash (&dkp->pub,
&cbc.denom_pub_hash);
RND_BLK (&cbc.reserve_sig);
+ 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);
- TALER_blinding_secret_create (&bks);
- GNUNET_assert (GNUNET_OK ==
- TALER_denom_blind (&dkp->pub,
- &bks,
- NULL, /* FIXME-Oec */
- &coin_pub,
- &c_hash,
- &pd.coin_ev,
- &pd.coin_ev_size));
- TALER_coin_ev_hash (pd.coin_ev,
- pd.coin_ev_size,
- &cbc.h_coin_envelope);
- GNUNET_assert (GNUNET_OK ==
- TALER_denom_sign_blinded (&cbc.sig,
- &dkp->priv,
- pd.coin_ev,
- pd.coin_ev_size));
- GNUNET_free (pd.coin_ev);
+ 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_set_zero (CURRENCY,
&cbc.withdraw_fee));
+
{
bool found;
+ bool nonce_reuse;
bool balance_ok;
- struct TALER_EXCHANGEDB_KycStatus kyc;
+ 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_withdraw (plugin->cls,
- &cbc,
- now,
- &found,
- &balance_ok,
- &kyc,
- &ruuid));
+ 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);
- GNUNET_assert (! kyc.ok);
}
+
+
FAILIF (GNUNET_OK !=
check_reserve (&reserve_pub,
0,
@@ -1485,6 +1480,8 @@ run (void *cls)
TALER_denom_sig_unblind (&ds,
&cbc2.sig,
&bks,
+ &c_hash,
+ alg_values,
&dkp->pub));
FAILIF (GNUNET_OK !=
TALER_denom_pub_verify (&dkp->pub,
@@ -1502,11 +1499,13 @@ run (void *cls)
TALER_denom_sig_unblind (&deposit.coin.denom_sig,
&cbc.sig,
&bks,
+ &c_hash,
+ alg_values,
&dkp->pub));
deadline = GNUNET_TIME_timestamp_get ();
{
- struct TALER_DenominationHash dph;
- struct TALER_AgeHash agh;
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
plugin->ensure_coin_known (plugin->cls,
@@ -1521,23 +1520,22 @@ run (void *cls)
struct GNUNET_TIME_Timestamp deposit_timestamp
= GNUNET_TIME_timestamp_get ();
bool balance_ok;
+ uint32_t bad_balance_idx;
bool in_conflict;
- struct TALER_PaytoHash h_payto;
+ struct TALER_PaytoHashP h_payto;
RND_BLK (&h_payto);
- deposit.refund_deadline
+ bd.refund_deadline
= GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MONTHS);
- deposit.wire_deadline
+ 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,
- &deposit,
- known_coin_id,
- &h_payto,
- false,
+ &bd,
&deposit_timestamp,
&balance_ok,
+ &bad_balance_idx,
&in_conflict));
FAILIF (! balance_ok);
FAILIF (in_conflict);
@@ -1550,17 +1548,17 @@ run (void *cls)
bool conflict;
refund.coin = deposit.coin;
- refund.details.merchant_pub = deposit.merchant_pub;
+ refund.details.merchant_pub = bd.merchant_pub;
RND_BLK (&refund.details.merchant_sig);
- refund.details.h_contract_terms = deposit.h_contract_terms;
+ refund.details.h_contract_terms = bd.h_contract_terms;
refund.details.rtransaction_id = 1;
refund.details.refund_amount = value;
- refund.details.refund_fee = fee_refund;
+ 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,
- &fee_deposit,
+ &fees.deposit,
known_coin_id,
&not_found,
&refund_ok,
@@ -1592,6 +1590,7 @@ run (void *cls)
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,
@@ -1615,7 +1614,7 @@ run (void *cls)
TALER_amount_cmp (&refresh.amount_with_fee,
&ret_refresh_session.session.amount_with_fee));
FAILIF (0 !=
- TALER_amount_cmp (&fee_refresh,
+ TALER_amount_cmp (&fees.refresh,
&ret_refresh_session.melt_fee));
FAILIF (0 !=
GNUNET_memcmp (&refresh.rc,
@@ -1654,35 +1653,43 @@ run (void *cls)
{
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,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund);
+ &fees);
GNUNET_assert (NULL != new_dkp[cnt]);
new_denom_pubs[cnt] = new_dkp[cnt]->pub;
ccoin = &revealed_coins[cnt];
- ccoin->coin_ev_size = 1 + (size_t) GNUNET_CRYPTO_random_u64 (
+ 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);
- ccoin->coin_ev = GNUNET_malloc (ccoin->coin_ev_size);
+ rsa->blinded_msg = GNUNET_malloc (rsa->blinded_msg_size);
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
- ccoin->coin_ev,
- ccoin->coin_ev_size);
- GNUNET_CRYPTO_hash (ccoin->coin_ev,
- ccoin->coin_ev_size,
- &ccoin->coin_envelope_hash.hash);
+ 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,
- ccoin->coin_ev,
- ccoin->coin_ev_size));
+ true,
+ bp));
}
RND_BLK (&tprivs);
RND_BLK (&tpub);
@@ -1700,13 +1707,15 @@ run (void *cls)
tprivs,
&tpub));
{
- struct TALER_BlindedCoinHash h_coin_ev;
+ struct TALER_BlindedCoinHashP h_coin_ev;
struct TALER_CoinSpendPublicKeyP ocp;
+ struct TALER_DenominationHashP denom_hash;
- GNUNET_CRYPTO_hash (revealed_coins[0].coin_ev,
- revealed_coins[0].coin_ev_size,
- &h_coin_ev.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,
@@ -1730,12 +1739,20 @@ run (void *cls)
/* 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,
- GNUNET_YES,
+ 0,
+ 0,
+ &etag,
+ &balance,
+ &h_denom_pub,
&tl);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
+ FAILIF (0 >= qs);
+ FAILIF (NULL == tl);
plugin->free_coin_transaction_list (plugin->cls,
tl);
}
@@ -1745,11 +1762,11 @@ run (void *cls)
{
struct GNUNET_TIME_Timestamp recoup_timestamp
= GNUNET_TIME_timestamp_get ();
- union TALER_DenominationBlindingKeyP coin_bks;
+ union GNUNET_CRYPTO_BlindingSecretP coin_bks;
uint64_t new_known_coin_id;
struct TALER_CoinPublicInfo new_coin;
- struct TALER_DenominationHash dph;
- struct TALER_AgeHash agh;
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
bool recoup_ok;
bool internal_failure;
@@ -1782,7 +1799,6 @@ run (void *cls)
struct TALER_EXCHANGEDB_Reserve pre_reserve;
struct TALER_EXCHANGEDB_Reserve post_reserve;
struct TALER_Amount delta;
- struct TALER_EXCHANGEDB_KycStatus kyc;
bool recoup_ok;
bool internal_failure;
struct GNUNET_TIME_Timestamp recoup_timestamp
@@ -1791,8 +1807,7 @@ run (void *cls)
pre_reserve.pub = reserve_pub;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->reserves_get (plugin->cls,
- &pre_reserve,
- &kyc));
+ &pre_reserve));
FAILIF (! TALER_amount_is_zero (&pre_reserve.balance));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->do_recoup (plugin->cls,
@@ -1810,8 +1825,7 @@ run (void *cls)
post_reserve.pub = reserve_pub;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->reserves_get (plugin->cls,
- &post_reserve,
- &kyc));
+ &post_reserve));
FAILIF (0 >=
TALER_amount_subtract (&delta,
&post_reserve.balance,
@@ -1842,7 +1856,8 @@ run (void *cls)
sndr,
&wire_out_wtid,
&amount_with_fee,
- &fee_closing));
+ &fee_closing,
+ 0));
FAILIF (GNUNET_OK !=
check_reserve (&reserve_pub2,
0,
@@ -1856,20 +1871,27 @@ run (void *cls)
sndr,
&wire_out_wtid,
&value,
- &fee_closing));
+ &fee_closing,
+ 0));
FAILIF (GNUNET_OK !=
check_reserve (&reserve_pub,
0,
0,
value.currency));
result = 7;
+ 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);
}
@@ -1935,8 +1957,29 @@ run (void *cls)
&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;
+ }
}
}
+ GNUNET_assert (4 == cnt);
FAILIF (4 != cnt);
auditor_row_cnt = 0;
@@ -1960,10 +2003,20 @@ run (void *cls)
&audit_refund_cb,
NULL));
FAILIF (1 != auditor_row_cnt);
- qs = plugin->get_coin_transactions (plugin->cls,
- &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;
@@ -1983,26 +2036,24 @@ run (void *cls)
&deposit.csig));
FAILIF (0 !=
GNUNET_memcmp (&have->merchant_pub,
- &deposit.merchant_pub));
+ &bd.merchant_pub));
FAILIF (0 !=
GNUNET_memcmp (&have->h_contract_terms,
- &deposit.h_contract_terms));
+ &bd.h_contract_terms));
FAILIF (0 !=
GNUNET_memcmp (&have->wire_salt,
- &deposit.wire_salt));
+ &bd.wire_salt));
FAILIF (GNUNET_TIME_timestamp_cmp (have->timestamp,
!=,
- deposit.timestamp));
+ bd.wallet_timestamp));
FAILIF (GNUNET_TIME_timestamp_cmp (have->refund_deadline,
!=,
- deposit.refund_deadline));
+ bd.refund_deadline));
FAILIF (GNUNET_TIME_timestamp_cmp (have->wire_deadline,
!=,
- deposit.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;
}
@@ -2073,7 +2124,6 @@ run (void *cls)
memset (&deposit,
0,
sizeof (deposit));
- deposit.deposit_fee = fee_deposit;
RND_BLK (&deposit.coin.coin_pub);
TALER_denom_pub_hash (&dkp->pub,
&deposit.coin.denom_pub_hash);
@@ -2081,27 +2131,30 @@ run (void *cls)
TALER_denom_sig_unblind (&deposit.coin.denom_sig,
&cbc.sig,
&bks,
+ &c_hash,
+ alg_values,
&dkp->pub));
RND_BLK (&deposit.csig);
- RND_BLK (&deposit.merchant_pub);
- RND_BLK (&deposit.h_contract_terms);
- RND_BLK (&deposit.wire_salt);
- deposit.receiver_wire_account =
+ 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",
- &deposit.wire_salt,
+ &bd.wire_salt,
&h_wire_wt);
deposit.amount_with_fee = value;
- deposit.deposit_fee = fee_deposit;
-
- deposit.refund_deadline = deadline;
- deposit.wire_deadline = deadline;
+ bd.refund_deadline = deadline;
+ bd.wire_deadline = deadline;
result = 8;
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "test-3"));
{
uint64_t known_coin_id;
- struct TALER_DenominationHash dph;
- struct TALER_AgeHash agh;
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
plugin->ensure_coin_known (plugin->cls,
@@ -2114,23 +2167,31 @@ run (void *cls)
struct GNUNET_TIME_Timestamp now;
struct GNUNET_TIME_Timestamp r;
struct TALER_Amount deposit_fee;
- struct TALER_MerchantWireHash h_wire;
+ 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->insert_deposit (plugin->cls,
- now,
- &deposit));
- TALER_merchant_wire_signature_hash (deposit.receiver_wire_account,
- &deposit.wire_salt,
+ 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,
- &deposit.h_contract_terms,
+ &bd.h_contract_terms,
&h_wire,
&deposit.coin.coin_pub,
- &deposit.merchant_pub,
- deposit.refund_deadline,
+ &bd.merchant_pub,
+ bd.refund_deadline,
&deposit_fee,
&r));
FAILIF (GNUNET_TIME_timestamp_cmp (now,
@@ -2138,111 +2199,168 @@ run (void *cls)
r));
}
{
- struct GNUNET_TIME_Timestamp start_range;
- struct GNUNET_TIME_Timestamp end_range;
-
- start_range = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_subtract (deadline.abs_time,
- GNUNET_TIME_UNIT_SECONDS));
- end_range = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (deadline.abs_time,
- GNUNET_TIME_UNIT_SECONDS));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->select_deposits_missing_wire (plugin->cls,
- start_range,
- end_range,
- &wire_missing_cb,
- &deposit));
+ 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_deposits_above_serial_id (plugin->cls,
- 0,
- &audit_deposit_cb,
- NULL));
+ 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 */
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_ready_deposit (plugin->cls,
- 0,
- INT32_MAX,
- true,
- &deposit_cb,
- &deposit));
- FAILIF (8 == result);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->iterate_matching_deposits (plugin->cls,
- wire_target_row,
- &deposit.merchant_pub,
- &matching_deposit_cb,
- &deposit,
- 2));
+ {
+ 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 !=
plugin->start (plugin->cls,
- "test-2"));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->mark_deposit_tiny (plugin->cls,
- deposit_rowid));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->get_ready_deposit (plugin->cls,
- 0,
- INT32_MAX,
- true,
- &deposit_cb,
- &deposit));
- plugin->rollback (plugin->cls);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_ready_deposit (plugin->cls,
- 0,
- INT32_MAX,
- true,
- &deposit_cb,
- &deposit));
- FAILIF (GNUNET_OK !=
- plugin->start (plugin->cls,
"test-3"));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->mark_deposit_done (plugin->cls,
- deposit_rowid));
+ {
+ 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;
- deposit2 = deposit;
FAILIF (GNUNET_OK !=
plugin->start (plugin->cls,
"test-2"));
- RND_BLK (&deposit2.merchant_pub); /* should fail if merchant is different */
+ RND_BLK (&mpub2); /* should fail if merchant is different */
{
- struct TALER_MerchantWireHash h_wire;
+ struct TALER_MerchantWireHashP h_wire;
struct GNUNET_TIME_Timestamp r;
struct TALER_Amount deposit_fee;
- TALER_merchant_wire_signature_hash (deposit2.receiver_wire_account,
- &deposit2.wire_salt,
+ 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,
- &deposit2.h_contract_terms,
+ &bd.h_contract_terms,
&h_wire,
- &deposit2.coin.coin_pub,
- &deposit2.merchant_pub,
- deposit2.refund_deadline,
+ &deposit.coin.coin_pub,
+ &mpub2,
+ bd.refund_deadline,
&deposit_fee,
&r));
- deposit2.merchant_pub = deposit.merchant_pub;
- RND_BLK (&deposit2.coin.coin_pub); /* should fail if coin is different */
+ RND_BLK (&cpub2); /* should fail if coin is different */
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->have_deposit2 (plugin->cls,
- &deposit2.h_contract_terms,
+ &bd.h_contract_terms,
&h_wire,
- &deposit2.coin.coin_pub,
- &deposit2.merchant_pub,
- deposit2.refund_deadline,
+ &cpub2,
+ &bd.merchant_pub,
+ bd.refund_deadline,
&deposit_fee,
&r));
}
@@ -2251,6 +2369,9 @@ run (void *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,
@@ -2289,7 +2410,7 @@ run (void *cls)
FAILIF (GNUNET_OK !=
test_wire_prepare ());
FAILIF (GNUNET_OK !=
- test_wire_out (&deposit));
+ test_wire_out (&bd));
FAILIF (GNUNET_OK !=
test_gc ());
FAILIF (GNUNET_OK !=
@@ -2308,6 +2429,7 @@ drop:
rh = NULL;
GNUNET_break (GNUNET_OK ==
plugin->drop_tables (plugin->cls));
+cleanup:
if (NULL != dkp)
destroy_denom_key_pair (dkp);
if (NULL != revealed_coins)
@@ -2315,7 +2437,7 @@ drop:
for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
{
TALER_blinded_denom_sig_free (&revealed_coins[cnt].coin_sig);
- GNUNET_free (revealed_coins[cnt].coin_ev);
+ TALER_blinded_planchet_free (&revealed_coins[cnt].blinded_planchet);
}
GNUNET_free (revealed_coins);
revealed_coins = NULL;
@@ -2352,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_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_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/exchangedb/withdraw.sql b/src/exchangedb/withdraw.sql
deleted file mode 100644
index 0ac9a73f0..000000000
--- a/src/exchangedb/withdraw.sql
+++ /dev/null
@@ -1,130 +0,0 @@
-CREATE OR REPLACE FUNCTION exchange_do_withdraw(
- IN amount_val INT8,
- IN amount_frac INT4,
- IN h_denom_pub BYTEA,
- IN rpub BYTEA,
- IN reserve_sig BYTEA,
- IN h_coin_envelope BYTEA,
- IN denom_sig BYTEA,
- IN now INT8,
- IN min_reserve_gc INT8,
- OUT reserve_found BOOLEAN,
- OUT balance_ok BOOLEAN,
- OUT kycok BOOLEAN,
- OUT ruuid INT8,
- OUT account_uuid INT8)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- reserve_gc INT8;
-DECLARE
- denom_serial INT8;
-DECLARE
- reserve_val INT8;
-DECLARE
- reserve_frac INT4;
-BEGIN
-
-
-SELECT denominations_serial INTO denom_serial
- FROM denominations
- WHERE denom_pub_hash=h_denom_pub;
-
-IF NOT FOUND
-THEN
- -- denomination unknown, should be impossible!
- reserve_found=FALSE;
- balance_ok=FALSE;
- kycok=FALSE;
- ruuid=0;
- account_uuid=0;
- ASSERT false, 'denomination unknown';
- RETURN;
-END IF;
-
-
-UPDATE reserves SET
- gc_date=GREATEST(gc_date, min_reserve_gc)
- ,current_balance_val=current_balance_val - amount_val
- - CASE WHEN (current_balance_frac < amount_frac)
- THEN 1
- ELSE 0
- END
- ,current_balance_frac=current_balance_frac - amount_frac
- + CASE WHEN (current_balance_frac < amount_frac)
- THEN 100000000
- ELSE 0
- END
- WHERE reserves.reserve_pub=rpub
- AND ( (current_balance_val > amount_val) OR
- ( (current_balance_val = amount_val) AND
- (current_balance_frac >= amount_frac) ) );
-
-balance_ok=FOUND;
-
--- Obtain KYC status based on the last wire transfer into
--- this reserve. FIXME: likely not adequate for reserves that got P2P transfers!
-SELECT
- kyc_ok
- ,wire_source_serial_id
- ,reserve_uuid
- INTO
- kycok
- ,account_uuid
- ,ruuid
- FROM reserves
- JOIN reserves_in USING (reserve_uuid)
- JOIN wire_targets ON (wire_source_serial_id = wire_target_serial_id)
- WHERE reserves.reserve_pub=rpub
- LIMIT 1; -- limit 1 should not be required (without p2p transfers)
-
-IF NOT FOUND
-THEN
- -- reserve unknown
- reserve_found=FALSE;
- balance_ok=FALSE;
- kycok=FALSE;
- account_uuid=0;
- RETURN;
-END IF;
-
-reserve_found=TRUE;
-
-
--- We optimistically insert, and then on conflict declare
--- the query successful due to idempotency.
-INSERT INTO reserves_out
- (h_blind_ev
- ,denominations_serial
- ,denom_sig
- ,reserve_uuid
- ,reserve_sig
- ,execution_date
- ,amount_with_fee_val
- ,amount_with_fee_frac)
-VALUES
- (h_coin_envelope
- ,denom_serial
- ,denom_sig
- ,ruuid
- ,reserve_sig
- ,now
- ,amount_val
- ,amount_frac)
-ON CONFLICT DO NOTHING;
-
-IF NOT FOUND
-THEN
- -- idempotent query, all constraints must be satisfied
- balance_ok=TRUE;
- -- rollback any potential balance update we may have made
- ROLLBACK;
- START TRANSACTION ISOLATION LEVEL SERIALIZABLE;
- RETURN;
-END IF;
-
-END $$;
-
-COMMENT ON FUNCTION exchange_do_withdraw(INT8, INT4, BYTEA, BYTEA, BYTEA, BYTEA, BYTEA, INT8, INT8)
- IS 'Checks whether the reserve has sufficient balance for a withdraw operation (or the request is repeated and was previously approved) and if so updates the database with the result';
-
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 13b9a1444..45d74ab8e 100644
--- a/src/include/Makefile.am
+++ b/src/include/Makefile.am
@@ -5,6 +5,7 @@ talerinclude_HEADERS = \
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 \
@@ -16,7 +17,10 @@ talerinclude_HEADERS = \
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 \
@@ -24,6 +28,7 @@ talerinclude_HEADERS = \
taler_pq_lib.h \
taler_signatures.h \
taler_sq_lib.h \
+ taler_templating_lib.h \
taler_twister_testing_lib.h
EXTRA_DIST = \
diff --git a/src/include/platform.h b/src/include/platform.h
index b2c286b74..db04cb972 100644
--- a/src/include/platform.h
+++ b/src/include/platform.h
@@ -15,22 +15,22 @@
*/
/**
- * @file exchange/src/include/platform.h
+ * @file include/platform.h
* @brief This file contains the includes and definitions which are used by the
* 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
@@ -45,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
@@ -67,24 +64,209 @@
#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
@@ -93,6 +275,25 @@
#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 c6d2f474e..937238d15 100644
--- a/src/include/taler_amount_lib.h
+++ b/src/include/taler_amount_lib.h
@@ -18,6 +18,10 @@
* @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
@@ -124,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
@@ -185,6 +199,18 @@ 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
@@ -386,7 +412,7 @@ TALER_amount_multiply (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);
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 f71520345..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-2021 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,6 +46,11 @@ 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
@@ -147,56 +154,90 @@ struct TALER_AUDITOR_HttpResponse
/**
+ * 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 hr HTTP response data
- * @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_HttpResponse *hr,
- 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);
/**
@@ -206,16 +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 hr HTTP response data
+ * @param dcr response data
*/
typedef void
(*TALER_AUDITOR_DepositConfirmationResultCallback)(
void *cls,
- const struct TALER_AUDITOR_HttpResponse *hr);
+ const struct TALER_AUDITOR_DepositConfirmationResponse *dcr);
/**
@@ -225,21 +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_extensions hash over the extensions, if any
+ * @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 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
@@ -255,15 +310,18 @@ typedef void
*/
struct TALER_AUDITOR_DepositConfirmationHandle *
TALER_AUDITOR_deposit_confirmation (
- struct TALER_AUDITOR_Handle *auditor,
- const struct TALER_MerchantWireHash *h_wire,
- const struct TALER_ExtensionContractHash *h_extensions,
- const struct TALER_PrivateContractHash *h_contract_terms,
- struct GNUNET_TIME_Timestamp timestamp,
+ 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 *amount_without_fee,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ 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,
@@ -287,72 +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 /exchanges.
- *
- * @param cls closure
- * @param hr HTTP response data
- * @param num_exchanges length of array at @a ei
- * @param ei information about exchanges returned by the auditor
- */
-typedef void
-(*TALER_AUDITOR_ListExchangesResultCallback)(
- void *cls,
- const struct TALER_AUDITOR_HttpResponse *hr,
- unsigned int num_exchanges,
- const struct TALER_AUDITOR_ExchangeInfo *ei);
-
-/**
- * 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 e4ff8e8e2..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-2021 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,26 +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_historic_denom_revenue()
*
* @param cls closure
@@ -62,7 +48,7 @@ typedef void
typedef enum GNUNET_GenericReturnValue
(*TALER_AUDITORDB_HistoricDenominationRevenueDataCallback)(
void *cls,
- const struct TALER_DenominationHash *denom_pub_hash,
+ 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);
@@ -90,262 +76,201 @@ typedef enum GNUNET_GenericReturnValue
/**
- * 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_Timestamp 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_Timestamp 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_Timestamp 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_Timestamp 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 TALER_PrivateContractHash h_contract_terms;
+ struct TALER_Amount reserve_balance;
/**
- * Hash over the extensions for the deposit.
+ * Losses from operations that should not have
+ * happened (e.g. negative balance).
*/
- struct TALER_ExtensionContractHash h_extensions;
+ struct TALER_Amount reserve_loss;
/**
- * Hash over the wiring information of the merchant.
+ * Fees charged for withdraw.
*/
- struct TALER_MerchantWireHash h_wire;
+ struct TALER_Amount withdraw_fee_balance;
/**
- * Time when this deposit confirmation was generated by the exchange.
+ * Fees charged for closing.
*/
- struct GNUNET_TIME_Timestamp exchange_timestamp;
+ struct TALER_Amount close_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 purse creation.
*/
- struct GNUNET_TIME_Timestamp refund_deadline;
+ struct TALER_Amount purse_fee_balance;
/**
- * How much time does the @e exchange have to wire the funds?
+ * Opening fees charged.
*/
- struct GNUNET_TIME_Timestamp wire_deadline;
+ struct TALER_Amount open_fee_balance;
/**
- * Amount to be deposited, excluding fee. Calculated from the
- * amount with fee and the fee from the deposit request.
+ * History fees charged.
*/
- struct TALER_Amount amount_without_fee;
+ struct TALER_Amount history_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).
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
+/**
+ * Balance data for denominations in circulation.
+ */
+struct TALER_AUDITORDB_DenominationCirculationData
+{
/**
- * The Merchant's public key. Allows the merchant to later refund
- * the transaction or to inquire about the wire transfer identifier.
+ * Amount of outstanding coins in circulation.
*/
- struct TALER_MerchantPublicKeyP merchant;
+ struct TALER_Amount denom_balance;
/**
- * Signature from the exchange of type
- * #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT.
+ * Amount lost due coins illicitly accepted (effectively, a
+ * negative @a denom_balance).
*/
- struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_Amount denom_loss;
/**
- * Public signing key from the exchange matching @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_ExchangePublicKeyP exchange_pub;
+ struct TALER_Amount denom_risk;
/**
- * Exchange master signature over @e exchange_sig.
+ * Amount lost due to recoups.
*/
- struct TALER_MasterSignatureP master_sig;
+ struct TALER_Amount recoup_loss;
/**
- * Master public key of the exchange corresponding to @e master_sig.
- * Identifies the exchange this is about.
+ * Number of coins of this denomination that the exchange signed into
+ * existence.
*/
- struct TALER_MasterPublicKeyP master_public_key;
-
+ uint64_t num_issued;
};
@@ -366,6 +291,42 @@ typedef enum GNUNET_GenericReturnValue
/**
+ * 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
+ */
+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);
+
+
+/**
* @brief The plugin API, returned from the plugin's "init" function.
* The argument given to "init" is simply a configuration handle.
*
@@ -400,6 +361,48 @@ struct TALER_AUDITORDB_Plugin
/**
+ * 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);
+
+
+ /**
* Drop all auditor tables OR deletes recoverable auditor state.
* This should only be used by testcases or when restarting the
* auditor from scratch.
@@ -412,17 +415,21 @@ struct TALER_AUDITORDB_Plugin
*/
enum GNUNET_GenericReturnValue
(*drop_tables)(void *cls,
- int drop_exchangelist);
+ 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
*/
enum GNUNET_GenericReturnValue
- (*create_tables)(void *cls);
+ (*create_tables)(void *cls,
+ bool support_partitions,
+ uint32_t num_partitions);
/**
@@ -467,106 +474,21 @@ struct TALER_AUDITORDB_Plugin
/**
- * Insert information about an exchange this auditor will be auditing.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @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,
- 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 master_pub master public key of the exchange
- * @return query result status
- */
- enum GNUNET_DB_QueryStatus
- (*delete_exchange)(void *cls,
- 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 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,
- 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 sk signing key information to store
- * @return query result status
- */
- enum GNUNET_DB_QueryStatus
- (*insert_exchange_signkey)(
- void *cls,
- 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 dc deposit confirmation information to store
- * @return query result status
- */
- enum GNUNET_DB_QueryStatus
- (*insert_deposit_confirmation)(
- void *cls,
- 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 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,
- const struct TALER_MasterPublicKeyP *master_public_key,
- uint64_t start_id,
- TALER_AUDITORDB_DepositConfirmationCallback 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 master_pub master key of the exchange
- * @param ppc 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_coin)(
+ (*insert_auditor_progress)(
void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointCoin *ppc);
+ const char *progress_key,
+ uint64_t progress_offset,
+ ...);
/**
@@ -574,282 +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 master_pub master key of the exchange
- * @param ppc 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_coin)(
+ (*update_auditor_progress)(
void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointCoin *ppc);
+ 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 master_pub master key of the exchange
- * @param[out] ppc 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_coin)(void *cls,
- 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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointReserve *ppr);
-
-
- /**
- * 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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointReserve *ppr);
+ (*get_auditor_progress)(void *cls,
+ const char *progress_key,
+ uint64_t *progress_offset,
+ ...);
/**
- * Get information about the progress of the auditor.
+ * 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 master_pub master key of the exchange
- * @param[out] ppr set to where the auditor is 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
- (*get_auditor_progress_reserve)(
- void *cls,
- 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 master_pub master key of the exchange
- * @param ppdc where is the auditor in processing
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*insert_auditor_progress_deposit_confirmation)(
- void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointDepositConfirmation *ppdc);
+ (*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 master_pub master key of the exchange
- * @param ppdc where is the auditor in processing
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*update_auditor_progress_deposit_confirmation)(
- void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointDepositConfirmation *ppdc);
-
-
- /**
- * Get information about the progress of the auditor.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param master_pub master key of the exchange
- * @param[out] ppdc set to where the auditor is in processing
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*get_auditor_progress_deposit_confirmation)(
- void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_ProgressPointDepositConfirmation *ppdc);
-
-
- /**
- * 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 master_pub master key of the exchange
- * @param ppa where is the auditor in processing
- * @return transaction status code
- */
- enum GNUNET_DB_QueryStatus
- (*insert_auditor_progress_aggregation)(
- void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_AUDITORDB_ProgressPointAggregation *ppa);
-
-
- /**
- * 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 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,
- 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 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,
- 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 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,
- 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 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,
- 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 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,
- 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 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,
- 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 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,
- 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 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,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct TALER_AUDITORDB_WireProgressPoint *pp);
+ uint64_t row_id);
/**
@@ -858,22 +640,18 @@ struct TALER_AUDITORDB_Plugin
*
* @param cls the @e cls of this struct with the plugin-specific state
* @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,
- 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_Timestamp 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);
/**
@@ -882,20 +660,16 @@ struct TALER_AUDITORDB_Plugin
*
* @param cls the @e cls of this struct with the plugin-specific state
* @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,
- 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_Timestamp 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);
/**
@@ -903,24 +677,20 @@ struct TALER_AUDITORDB_Plugin
*
* @param cls the @e cls of this struct with the plugin-specific state
* @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,
- 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_Timestamp *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);
/**
@@ -928,277 +698,202 @@ struct TALER_AUDITORDB_Plugin
*
* @param cls the @e cls of this struct with the plugin-specific state
* @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,
- 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 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,
- 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 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,
- 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 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,
- 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 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,
- 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 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,
- 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 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,
- 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 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,
- const struct
- TALER_DenominationHash *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 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,
- const struct
- TALER_DenominationHash *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 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,
- const struct
- TALER_DenominationHash *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 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,
- const struct
- TALER_DenominationHash *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 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,
- 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 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,
- 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 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,
- 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);
/**
@@ -1206,7 +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 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
@@ -1218,19 +912,16 @@ struct TALER_AUDITORDB_Plugin
enum GNUNET_DB_QueryStatus
(*insert_historic_denom_revenue)(
void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
- const struct TALER_DenominationHash *denom_pub_hash,
+ 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 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
@@ -1238,7 +929,6 @@ struct TALER_AUDITORDB_Plugin
enum GNUNET_DB_QueryStatus
(*select_historic_denom_revenue)(
void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
TALER_AUDITORDB_HistoricDenominationRevenueDataCallback cb,
void *cb_cls);
@@ -1247,7 +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 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
@@ -1256,7 +945,6 @@ struct TALER_AUDITORDB_Plugin
enum GNUNET_DB_QueryStatus
(*insert_historic_reserve_revenue)(
void *cls,
- const struct TALER_MasterPublicKeyP *master_pub,
struct GNUNET_TIME_Timestamp start_time,
struct GNUNET_TIME_Timestamp end_time,
const struct TALER_Amount *reserve_profits);
@@ -1266,7 +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 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
@@ -1274,55 +961,9 @@ struct TALER_AUDITORDB_Plugin
enum GNUNET_DB_QueryStatus
(*select_historic_reserve_revenue)(
void *cls,
- 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 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,
- 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 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,
- 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 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,
- 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 bb7f3d33b..e8e32947b 100644
--- a/src/include/taler_bank_service.h
+++ b/src/include/taler_bank_service.h
@@ -100,25 +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_Timestamp timestamp,
- const json_t *json);
+ const struct TALER_BANK_AdminAddIncomingResponse *air);
/**
@@ -191,21 +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_Timestamp timestamp);
+ const struct TALER_BANK_TransferResponse *tr);
/**
@@ -261,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;
@@ -271,22 +358,72 @@ struct TALER_BANK_CreditDetails
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
+{
+
/**
- * 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_uri;
+ 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://-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;
+
};
@@ -295,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 enum GNUNET_GenericReturnValue
+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);
/**
@@ -370,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;
@@ -390,16 +519,66 @@ struct TALER_BANK_DebitDetails
const char *exchange_base_url;
/**
- * payto://-URI of the source account that
- * send the funds.
+ * payto://-URI of the target account that received the funds.
*/
- const char *debit_account_uri;
+ const char *credit_account_uri;
+
+};
+
+
+/**
+ * Response details for a history request.
+ */
+struct TALER_BANK_DebitHistoryResponse
+{
/**
- * payto://-URI 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_uri;
+ 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;
};
@@ -409,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 enum GNUNET_GenericReturnValue
+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);
/**
diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h
index f1fa0285d..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-2021 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,13 +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_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 ************* */
@@ -44,6 +119,26 @@ struct TALER_SecurityModulePublicKeyP
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).
@@ -106,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.
@@ -159,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
@@ -172,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.
*/
@@ -222,6 +355,30 @@ struct TALER_MasterPublicKeyP
/**
+ * @brief Type of the offline master public keys used by the exchange.
+ */
+struct TALER_MasterPrivateKeyP
+{
+ /**
+ * Taler uses EdDSA for the long-term offline master key.
+ */
+ struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of signatures by the offline master public key used by the exchange.
+ */
+struct TALER_MasterSignatureP
+{
+ /**
+ * Taler uses EdDSA for the long-term offline master key.
+ */
+ struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
+};
+
+
+/**
* @brief Type of the private key used by the auditor.
*/
struct TALER_AuditorPrivateKeyP
@@ -258,108 +415,201 @@ struct TALER_AuditorSignatureP
/**
- * @brief Type of the offline master public keys used by the exchange.
+ * @brief Type of public keys for Taler coins. The same key material is used
+ * for EdDSA and ECDHE operations.
*/
-struct TALER_MasterPrivateKeyP
+struct TALER_CoinSpendPublicKeyP
{
/**
- * Taler uses EdDSA for the long-term offline master key.
+ * Taler uses EdDSA for coins when signing deposit requests.
*/
- struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+ struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+
};
/**
- * @brief Type of signatures by the offline master public key used by the exchange.
+ * @brief Type of private keys for Taler coins. The same key material is used
+ * for EdDSA and ECDHE operations.
*/
-struct TALER_MasterSignatureP
+struct TALER_CoinSpendPrivateKeyP
{
/**
- * Taler uses EdDSA for the long-term offline master key.
+ * Taler uses EdDSA for coins when signing deposit requests.
+ */
+ struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+/**
+ * @brief Type of signatures made with Taler coins.
+ */
+struct TALER_CoinSpendSignatureP
+{
+ /**
+ * Taler uses EdDSA for coins.
*/
struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
};
-/*
- * @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-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 exchange does not support the extension for
- * age-restriction.
+
+/**
+ * @brief Type of private keys for age commitment in coins.
*/
-struct TALER_AgeMask
+struct TALER_AgeCommitmentPrivateKeyP
{
- uint32_t mask;
+#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 Age restriction commitment of a coin.
+ * @brief Type of public keys for age commitment in coins.
*/
-struct TALER_AgeHash
+struct TALER_AgeCommitmentPublicKeyP
{
+#ifdef AGE_RESTRICTION_WITH_ECDSA
/**
- * The commitment is a SHA-256 hash code.
+ * Taler uses EcDSA for coins when signing age verification attestation.
*/
- struct GNUNET_ShortHashCode shash;
+ 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 public keys for Taler coins. The same key material is used
- * for EdDSA and ECDHE operations.
+ * @brief Type of online public keys used by the wallet to establish a purse and the associated contract meta data.
*/
-struct TALER_CoinSpendPublicKeyP
+struct TALER_PurseContractPublicKeyP
{
/**
- * Taler uses EdDSA for coins when signing deposit requests.
+ * Taler uses EdDSA for purse message signing.
*/
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 private keys for Taler coins. The same key material is used
- * for EdDSA and ECDHE operations.
+ * @brief Type of signatures used by the wallet to sign purse creation messages online.
*/
-struct TALER_CoinSpendPrivateKeyP
+struct TALER_PurseContractSignatureP
{
/**
- * Taler uses EdDSA for coins when signing deposit requests.
+ * 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 made with Taler coins.
+ * @brief Type of signatures used by the wallet to sign purse merge requests online.
*/
-struct TALER_CoinSpendSignatureP
+struct TALER_PurseMergeSignatureP
{
/**
- * Taler uses EdDSA for coins.
+ * Taler uses EdDSA for online signatures sessions.
*/
struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
};
/**
- * @brief Type of blinding keys for Taler.
+ * @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.
*/
-union TALER_DenominationBlindingKeyP
+struct TALER_AmlOfficerSignatureP
{
/**
- * Taler uses RSA for blind signatures.
+ * Taler uses EdDSA for AML decision signing.
*/
- struct GNUNET_CRYPTO_RsaBlindingKeySecret rsa_bks;
+ struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
};
@@ -377,6 +627,19 @@ 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.
*/
@@ -393,7 +656,7 @@ struct TALER_ClaimTokenP
* Salt used to hash a merchant's payto:// URI to
* compute the "h_wire" (say for deposit requests).
*/
-struct TALER_WireSalt
+struct TALER_WireSaltP
{
/**
* Actual 128-bit salt value.
@@ -403,6 +666,20 @@ struct TALER_WireSalt
/**
+ * 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.
@@ -417,21 +694,41 @@ struct TALER_RsaPubHashP
/**
- * Hash @a rsa.
- *
- * @param rsa key to hash
- * @param[out] h_rsa where to write the result
+ * Master key material for the deriviation of
+ * private coins and blinding factors during
+ * withdraw or refresh.
*/
-void
-TALER_rsa_pub_hash (const struct GNUNET_CRYPTO_RsaPublicKey *rsa,
- struct TALER_RsaPubHashP *h_rsa);
+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_DenominationHash
+struct TALER_DenominationHashP
{
/**
* Actual hash value.
@@ -444,7 +741,7 @@ struct TALER_DenominationHash
* Hash used to represent the private part
* of a contract between merchant and consumer.
*/
-struct TALER_PrivateContractHash
+struct TALER_PrivateContractHashP
{
/**
* Actual hash value.
@@ -454,10 +751,9 @@ struct TALER_PrivateContractHash
/**
- * Hash used to represent the "public" extensions to
- * a contract that is shared with the exchange.
+ * Hash used to represent the policy extension to a deposit
*/
-struct TALER_ExtensionContractHash
+struct TALER_ExtensionPolicyHashP
{
/**
* Actual hash value.
@@ -470,7 +766,7 @@ struct TALER_ExtensionContractHash
* Hash used to represent the salted hash of a
* merchant's bank account.
*/
-struct TALER_MerchantWireHash
+struct TALER_MerchantWireHashP
{
/**
* Actual hash value.
@@ -483,12 +779,12 @@ struct TALER_MerchantWireHash
* Hash used to represent the unsalted hash of a
* payto:// URI representing a bank account.
*/
-struct TALER_PaytoHash
+struct TALER_PaytoHashP
{
/**
* Actual hash value.
*/
- struct GNUNET_HashCode hash;
+ struct GNUNET_ShortHashCode hash;
};
@@ -496,7 +792,7 @@ struct TALER_PaytoHash
* Hash used to represent a commitment to a blinded
* coin, i.e. the hash of the envelope.
*/
-struct TALER_BlindedCoinHash
+struct TALER_BlindedCoinHashP
{
/**
* Actual hash value.
@@ -509,7 +805,7 @@ struct TALER_BlindedCoinHash
* Hash used to represent the hash of the public
* key of a coin (without blinding).
*/
-struct TALER_CoinPubHash
+struct TALER_CoinPubHashP
{
/**
* Actual hash value.
@@ -519,9 +815,9 @@ struct TALER_CoinPubHash
/**
- * @brief Value that uniquely identifies a tip.
+ * @brief Value that uniquely identifies a reward.
*/
-struct TALER_TipIdentifierP
+struct TALER_RewardIdentifierP
{
/**
* The tip identifier is a SHA-512 hash code.
@@ -543,10 +839,10 @@ struct TALER_PickupIdentifierP
/**
- * @brief Salted hash over the JSON object representing the configuration of an
- * extension.
+ * @brief Salted hash over the JSON object representing the manifests of
+ * extensions.
*/
-struct TALER_ExtensionConfigHash
+struct TALER_ExtensionManifestsHashP
{
/**
* Actual hash value.
@@ -555,97 +851,399 @@ struct TALER_ExtensionConfigHash
};
-GNUNET_NETWORK_STRUCT_END
+/**
+ * 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;
+
+};
/**
- * Types of public keys used for denominations in Taler.
+ * Set of the fees applying globally for a given
+ * time-range.
*/
-enum TALER_DenominationCipher
+struct TALER_GlobalFeeSetNBOP
{
/**
- * Invalid type of signature.
+ * The fee the exchange charges for returning the history of a reserve or
+ * account.
*/
- TALER_DENOMINATION_INVALID = 0,
+ struct TALER_AmountNBO history;
/**
- * RSA blind signature.
+ * The fee the exchange charges for keeping an account or reserve open for a
+ * year.
*/
- TALER_DENOMINATION_RSA = 1,
+ struct TALER_AmountNBO account;
/**
- * Clause-Schnorr blind signature.
+ * The fee the exchange charges if a purse is abandoned and this was not
+ * covered by the account limit.
*/
- // TALER_DENOMINATION_CS = 2
+ struct TALER_AmountNBO purse;
};
+GNUNET_NETWORK_STRUCT_END
+
+
/**
- * @brief Type of (unblinded) coin signatures for Taler.
+ * 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
*/
-struct TALER_DenominationSignature
+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
{
/**
- * Type of the signature.
+ * The fee the exchange charges when a coin of this type is withdrawn.
+ * (can be zero).
*/
- enum TALER_DenominationCipher cipher;
+ struct TALER_Amount withdraw;
/**
- * Details, depending on @e cipher.
+ * The fee the exchange charges when a coin of this type is deposited.
+ * (can be zero).
*/
- union
- {
+ struct TALER_Amount deposit;
- /**
- * If we use #TALER_DENOMINATION_RSA in @a cipher.
- */
- struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
+ /**
+ * The fee the exchange charges when a coin of this type is refreshed.
+ * (can be zero).
+ */
+ struct TALER_Amount refresh;
- } details;
+ /**
+ * 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;
};
/**
- * @brief Type for *blinded* denomination signatures for Taler.
- * Must be unblinded before it becomes valid.
+ * Set of the fees applying for a given time-range and wire method.
*/
-struct TALER_BlindedDenominationSignature
+struct TALER_WireFeeSet
{
/**
- * Type of the signature.
+ * 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.
*/
- enum TALER_DenominationCipher cipher;
+ struct TALER_Amount account;
/**
- * Details, depending on @e cipher.
+ * The fee the exchange charges if a purse
+ * is abandoned and this was not covered by
+ * the account limit.
*/
- union
- {
+ struct TALER_Amount purse;
+};
- /**
- * If we use #TALER_DENOMINATION_RSA in @a cipher.
- */
- struct GNUNET_CRYPTO_RsaSignature *blinded_rsa_signature;
- } details;
+/**
+ * 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
+{
+ /**
+ * Denominations use blind signatures.
+ */
+ struct GNUNET_CRYPTO_UnblindedSignature *unblinded_sig;
};
/**
- * @brief Type of public signing keys for verifying blindly signed coins.
+ * @brief Type for *blinded* denomination signatures for Taler.
+ * Must be unblinded before it becomes valid.
*/
-struct TALER_DenominationPublicKey
+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
+{
/**
- * Type of the public key.
+ * The commitment is a SHA-256 hash code.
*/
- enum TALER_DenominationCipher cipher;
+ 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
+{
/**
* Age restriction mask used for the key.
@@ -653,17 +1251,10 @@ struct TALER_DenominationPublicKey
struct TALER_AgeMask age_mask;
/**
- * Details, depending on @e cipher.
+ * Type of the public key.
*/
- union
- {
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bsign_pub_key;
- /**
- * If we use #TALER_DENOMINATION_RSA in @a cipher.
- */
- struct GNUNET_CRYPTO_RsaPublicKey *rsa_public_key;
-
- } details;
};
@@ -673,23 +1264,21 @@ struct TALER_DenominationPublicKey
struct TALER_DenominationPrivateKey
{
- /**
- * Type of the public key.
- */
- enum TALER_DenominationCipher cipher;
+ struct GNUNET_CRYPTO_BlindSignPrivateKey *bsign_priv_key;
+
+};
+
+/**
+ * @brief Blinded planchet send to exchange for blind signing.
+ */
+struct TALER_BlindedPlanchet
+{
/**
- * Details, depending on @e cipher.
+ * A blinded message.
*/
- union
- {
-
- /**
- * If we use #TALER_DENOMINATION_RSA in @a cipher.
- */
- struct GNUNET_CRYPTO_RsaPrivateKey *rsa_private_key;
+ struct GNUNET_CRYPTO_BlindedMessage *blinded_message;
- } details;
};
@@ -709,12 +1298,18 @@ struct TALER_CoinPublicInfo
* Hash of the public key representing the denomination of the coin that is
* being deposited.
*/
- struct TALER_DenominationHash 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;
/**
- * Hash of the age commitment.
+ * True, if age commitment is not applicable.
*/
- struct TALER_AgeHash age_commitment_hash;
+ bool no_age_commitment;
/**
* (Unblinded) signature over @e coin_pub with @e denom_pub,
@@ -733,7 +1328,7 @@ struct TALER_TrackTransferDetails
/**
* Hash of the proposal data.
*/
- struct TALER_PrivateContractHash h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
/**
* Which coin was deposited?
@@ -741,57 +1336,139 @@ 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;
+
};
/**
- * Free internals of @a denom_pub, but not @a denom_pub itself.
+ * @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.
*
- * @param[in] denom_pub key to free
+ * @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_pub_free (struct TALER_DenominationPublicKey *denom_pub);
+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);
/**
- * Create a blinding secret @a bs for @a cipher.
+ * @brief Method to derive withdraw /csr nonce
*
- * @param[out] bs blinding secret to initialize
+ * @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_blinding_secret_create (union TALER_DenominationBlindingKeyP *bs);
+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 #TALER_DENOMINATION_RSA, an additional "unsigned int"
+ * 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] deonm_pub where to write the public key
+ * @param[out] denom_pub where to write the public key
* @param cipher which type of cipher to use
- * @param ... cipher-specific parameters
+ * @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 TALER_DenominationCipher cipher,
+ 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
@@ -812,23 +1489,30 @@ 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] coin_ev blinded coin to submit
- * @param[out] coin_ev_size number of bytes in @a coin_ev
+ * @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 TALER_DenominationBlindingKeyP *coin_bks,
- const struct TALER_AgeHash *age_commitment_hash,
+ 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,
- struct TALER_CoinPubHash *c_hash,
- void **coin_ev,
- size_t *coin_ev_size);
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ struct TALER_CoinPubHashP *c_hash,
+ struct TALER_BlindedPlanchet *blinded_planchet);
/**
@@ -836,15 +1520,15 @@ TALER_denom_blind (const struct TALER_DenominationPublicKey *dk,
*
* @param[out] denom_sig where to write the signature
* @param denom_priv private key to use for signing
- * @param blinded_msg message to sign
- * @param blinded_msg_size number of bytes in @a blinded_msg
+ * @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,
- void *blinded_msg,
- size_t blinded_msg_size);
+ bool for_melt,
+ const struct TALER_BlindedPlanchet *blinded_planchet);
/**
@@ -853,6 +1537,8 @@ TALER_denom_sign_blinded (struct TALER_BlindedDenominationSignature *denom_sig,
* @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
*/
@@ -860,7 +1546,9 @@ enum GNUNET_GenericReturnValue
TALER_denom_sig_unblind (
struct TALER_DenominationSignature *denom_sig,
const struct TALER_BlindedDenominationSignature *bdenom_sig,
- const union TALER_DenominationBlindingKeyP *bks,
+ const union GNUNET_CRYPTO_BlindingSecretP *bks,
+ const struct TALER_CoinPubHashP *c_hash,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
const struct TALER_DenominationPublicKey *denom_pub);
@@ -882,7 +1570,7 @@ TALER_blinded_denom_sig_free (
*/
void
TALER_denom_pub_hash (const struct TALER_DenominationPublicKey *denom_pub,
- struct TALER_DenominationHash *denom_hash);
+ struct TALER_DenominationHashP *denom_hash);
/**
@@ -890,11 +1578,11 @@ TALER_denom_pub_hash (const struct TALER_DenominationPublicKey *denom_pub,
* @a denom_dst.
*
* @param[out] denom_dst target to copy to
- * @param denom_str public key to copy
+ * @param denom_src public key to copy
*/
void
-TALER_denom_pub_deep_copy (struct TALER_DenominationPublicKey *denom_dst,
- const struct TALER_DenominationPublicKey *denom_src);
+TALER_denom_pub_copy (struct TALER_DenominationPublicKey *denom_dst,
+ const struct TALER_DenominationPublicKey *denom_src);
/**
@@ -902,11 +1590,11 @@ TALER_denom_pub_deep_copy (struct TALER_DenominationPublicKey *denom_dst,
* @a denom_dst.
*
* @param[out] denom_dst target to copy to
- * @param denom_str public key to copy
+ * @param denom_src public key to copy
*/
void
-TALER_denom_sig_deep_copy (struct TALER_DenominationSignature *denom_dst,
- const struct TALER_DenominationSignature *denom_src);
+TALER_denom_sig_copy (struct TALER_DenominationSignature *denom_dst,
+ const struct TALER_DenominationSignature *denom_src);
/**
@@ -914,10 +1602,10 @@ TALER_denom_sig_deep_copy (struct TALER_DenominationSignature *denom_dst,
* @a denom_dst.
*
* @param[out] denom_dst target to copy to
- * @param denom_str public key to copy
+ * @param denom_src public key to copy
*/
void
-TALER_blinded_denom_sig_deep_copy (
+TALER_blinded_denom_sig_copy (
struct TALER_BlindedDenominationSignature *denom_dst,
const struct TALER_BlindedDenominationSignature *denom_src);
@@ -960,16 +1648,16 @@ TALER_blinded_denom_sig_cmp (
/**
- * Obtain denomination public key from a denomination private key.
+ * Compare two blinded planchets.
*
- * @param denom_priv private key to convert
- * @param age_mask age mask to be applied
- * @param[out] denom_pub where to return the public key
+ * @param bp1 first blinded planchet
+ * @param bp2 second blinded planchet
+ * @return 0 if the keys are equal, otherwise -1 or 1
*/
-void
-TALER_denom_priv_to_pub (const struct TALER_DenominationPrivateKey *denom_priv,
- const struct TALER_AgeMask age_mask,
- struct TALER_DenominationPublicKey *denom_pub);
+int
+TALER_blinded_planchet_cmp (
+ const struct TALER_BlindedPlanchet *bp1,
+ const struct TALER_BlindedPlanchet *bp2);
/**
@@ -984,7 +1672,52 @@ TALER_denom_priv_to_pub (const struct TALER_DenominationPrivateKey *denom_priv,
enum GNUNET_GenericReturnValue
TALER_denom_pub_verify (const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_CoinPubHash *c_hash);
+ 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);
/**
@@ -1005,14 +1738,14 @@ TALER_test_coin_valid (const struct TALER_CoinPublicInfo *coin_public_info,
/**
* Compute the hash of a blinded coin.
*
- * @param coin_ev blinded coin
- * @param coin_ev_size number of bytes in @a coin_ev
+ * @param blinded_planchet blinded planchet
+ * @param denom_hash hash of the denomination public key
* @param[out] bch where to write the hash
*/
void
-TALER_coin_ev_hash (const void *coin_ev,
- size_t coin_ev_size,
- struct TALER_BlindedCoinHash *bch);
+TALER_coin_ev_hash (const struct TALER_BlindedPlanchet *blinded_planchet,
+ const struct TALER_DenominationHashP *denom_hash,
+ struct TALER_BlindedCoinHashP *bch);
/**
@@ -1024,8 +1757,8 @@ TALER_coin_ev_hash (const void *coin_ev,
*/
void
TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_AgeHash *age_commitment_hash,
- struct TALER_CoinPubHash *coin_h);
+ const struct TALER_AgeCommitmentHash *age_commitment_hash,
+ struct TALER_CoinPubHashP *coin_h);
/**
@@ -1036,34 +1769,7 @@ TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,
*/
void
TALER_payto_hash (const char *payto,
- struct TALER_PaytoHash *h_payto);
-
-
-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.
- */
-struct TALER_PlanchetSecretsP
-{
-
- /**
- * Private key of the coin.
- */
- struct TALER_CoinSpendPrivateKeyP coin_priv;
-
- /**
- * The blinding key.
- */
- union TALER_DenominationBlindingKeyP blinding_key;
-
-};
-
-
-GNUNET_NETWORK_STRUCT_END
+ struct TALER_PaytoHashP *h_payto);
/**
@@ -1077,17 +1783,12 @@ struct TALER_PlanchetDetail
/**
* Hash of the denomination public key.
*/
- struct TALER_DenominationHash denom_pub_hash;
-
- /**
- * Blinded coin (see GNUNET_CRYPTO_rsa_blind()). Note: is malloc()'ed!
- */
- void *coin_ev;
+ struct TALER_DenominationHashP denom_pub_hash;
/**
- * Number of bytes in @a coin_ev.
+ * The blinded planchet
*/
- size_t coin_ev_size;
+ struct TALER_BlindedPlanchet blinded_planchet;
};
@@ -1110,8 +1811,9 @@ struct TALER_FreshCoin
struct TALER_CoinSpendPrivateKeyP coin_priv;
/**
- * FIXME-Oec: Age-verification vector, as pointer: Dyn alloc!
+ * Optional hash of an age commitment bound to this coin, maybe NULL.
*/
+ const struct TALER_AgeCommitmentHash *h_age_commitment;
};
@@ -1204,44 +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);
/**
- * Setup information for a fresh coin.
+ * 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 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);
/**
- * Prepare a planchet for tipping. Creates and blinds a coin.
+ * 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);
+
+
+/**
+ * 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
*/
enum GNUNET_GenericReturnValue
-TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,
- const struct TALER_PlanchetSecretsP *ps,
- struct TALER_CoinPubHash *c_hash,
- struct TALER_PlanchetDetail *pd);
+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);
/**
@@ -1250,8 +2029,11 @@ 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
*/
@@ -1259,12 +2041,24 @@ enum GNUNET_GenericReturnValue
TALER_planchet_to_coin (
const struct TALER_DenominationPublicKey *dk,
const struct TALER_BlindedDenominationSignature *blind_sig,
- const struct TALER_PlanchetSecretsP *ps,
- const struct TALER_CoinPubHash *c_hash,
+ 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);
/**
@@ -1327,14 +2121,9 @@ struct TALER_RefreshCoinData
const struct TALER_DenominationPublicKey *dk;
/**
- * The envelope with the blinded coin.
+ * The blinded planchet (details depend on cipher).
*/
- void *coin_ev;
-
- /**
- * Number of bytes in @a coin_ev
- */
- size_t coin_ev_size;
+ struct TALER_BlindedPlanchet blinded_planchet;
};
@@ -1362,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
@@ -1370,11 +2160,171 @@ 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 **************** */
/**
@@ -1395,7 +2345,7 @@ struct TALER_CRYPTO_RsaDenominationHelper;
* @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 denom_pub the public key itself, NULL if the key was revoked or 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.
@@ -1407,7 +2357,7 @@ typedef void
struct GNUNET_TIME_Timestamp start_time,
struct GNUNET_TIME_Relative validity_duration,
const struct TALER_RsaPubHashP *h_rsa,
- const struct TALER_DenominationPublicKey *denom_pub,
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
const struct TALER_SecurityModulePublicKeyP *sm_pub,
const struct TALER_SecurityModuleSignatureP *sm_sig);
@@ -1416,6 +2366,7 @@ typedef void
* 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).
@@ -1423,6 +2374,7 @@ typedef void
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);
@@ -1441,8 +2393,30 @@ TALER_CRYPTO_helper_rsa_poll (struct TALER_CRYPTO_RsaDenominationHelper *dh);
/**
- * Request helper @a dh to sign @a msg using the public key corresponding to
- * @a h_denom_pub.
+ * 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
@@ -1451,24 +2425,48 @@ TALER_CRYPTO_helper_rsa_poll (struct TALER_CRYPTO_RsaDenominationHelper *dh);
* differences in the signature counters. Retrying in this case may work.
*
* @param dh helper process connection
- * @param h_rsa hash of the RSA public key to use to sign
- * @param msg message to sign
- * @param msg_size number of bytes in @a msg
- * @param[out] ec set to the error code (or #TALER_EC_NONE on success)
- * @return signature, the value inside the structure will be NULL on failure,
- * see @a ec for details about the failure
+ * @param rsr details about the requested signature
+ * @param[out] bs set to the blind signature
+ * @return #TALER_EC_NONE on success
*/
-struct TALER_BlindedDenominationSignature
+enum TALER_ErrorCode
TALER_CRYPTO_helper_rsa_sign (
struct TALER_CRYPTO_RsaDenominationHelper *dh,
- const struct TALER_RsaPubHashP *h_rsa,
- const void *msg,
- size_t msg_size,
- enum TALER_ErrorCode *ec);
+ const struct TALER_CRYPTO_RsaSignRequest *rsr,
+ struct TALER_BlindedDenominationSignature *bs);
/**
- * Ask the helper to revoke the public key associated with @param h_denom_pub .
+ * 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.
*
@@ -1498,6 +2496,235 @@ 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.
*/
@@ -1532,6 +2759,7 @@ typedef void
* 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).
@@ -1539,6 +2767,7 @@ typedef void
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);
@@ -1609,7 +2838,7 @@ TALER_CRYPTO_helper_esign_sign_ (
/**
- * Ask the helper to revoke the public key @param exchange_pub .
+ * 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.
*
@@ -1638,41 +2867,510 @@ TALER_CRYPTO_helper_esign_disconnect (
struct TALER_CRYPTO_ExchangeSignHelper *esh);
-/* ********************* exchange signing ************************** */
+/* ********************* wallet signing ************************** */
/**
- * Verify a deposit confirmation.
+ * Sign a request to create a purse.
*
- * @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_extensions hash over the extensions, 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 amount_without_fee the amount to be deposited after fees
- * @param coin_pub public key of the deposited coin
- * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
- * @param exchange_pub exchange's online signing public key
- * @param exchange_sig the signature made with purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT
+ * @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_exchange_deposit_confirm_verify (
- const struct TALER_PrivateContractHash *h_contract_terms,
- const struct TALER_MerchantWireHash *h_wire,
- const struct TALER_ExtensionContractHash *h_extensions,
- 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,
+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_MerchantPublicKeyP *merchant_pub,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const struct TALER_ExchangeSignatureP *exchange_sig);
+ const struct TALER_CoinSpendSignatureP *coin_sig);
-/* ********************* wallet signing ************************** */
+/**
+ * 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);
/**
@@ -1682,7 +3380,9 @@ TALER_exchange_deposit_confirm_verify (
* @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 h_extensions hash over the extensions
+ * @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
@@ -1694,10 +3394,12 @@ void
TALER_wallet_deposit_sign (
const struct TALER_Amount *amount,
const struct TALER_Amount *deposit_fee,
- const struct TALER_MerchantWireHash *h_wire,
- const struct TALER_PrivateContractHash *h_contract_terms,
- const struct TALER_ExtensionContractHash *h_extensions,
- const struct TALER_DenominationHash *h_denom_pub,
+ 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,
@@ -1712,7 +3414,9 @@ TALER_wallet_deposit_sign (
* @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 h_extensions hash over the extensions
+ * @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)
@@ -1725,10 +3429,12 @@ enum GNUNET_GenericReturnValue
TALER_wallet_deposit_verify (
const struct TALER_Amount *amount,
const struct TALER_Amount *deposit_fee,
- const struct TALER_MerchantWireHash *h_wire,
- const struct TALER_PrivateContractHash *h_contract_terms,
- const struct TALER_ExtensionContractHash *h_extensions,
- const struct TALER_DenominationHash *h_denom_pub,
+ 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,
@@ -1739,10 +3445,11 @@ TALER_wallet_deposit_verify (
/**
* Sign a melt request.
*
- * @param amount the amount to be melted (with fee)
+ * @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
*/
@@ -1751,7 +3458,8 @@ 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_DenominationHash *h_denom_pub,
+ 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);
@@ -1759,10 +3467,11 @@ TALER_wallet_melt_sign (
/**
* Verify a melt request.
*
- * @param amount the amount to be melted (with fee)
+ * @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
@@ -1772,7 +3481,8 @@ 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_DenominationHash *h_denom_pub,
+ 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);
@@ -1782,18 +3492,17 @@ TALER_wallet_melt_verify (
*
* @param h_denom_pub hash of the denomiantion public key of the new coin
* @param transfer_pub transfer public key
- * @param coin_ev coin envelope
- * @param coin_ev_size number of bytes in @a coin_ev
+ * @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_DenominationHash *h_denom_pub,
- const struct TALER_TransferPublicKeyP *transfer_pub,
- const void *coin_ev,
- size_t coin_ev_size,
- const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
- struct TALER_CoinSpendSignatureP *coin_sig);
+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);
/**
@@ -1808,30 +3517,104 @@ TALER_wallet_link_sign (const struct TALER_DenominationHash *h_denom_pub,
*/
enum GNUNET_GenericReturnValue
TALER_wallet_link_verify (
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_TransferPublicKeyP *transfer_pub,
- const struct TALER_BlindedCoinHash *h_coin_ev,
+ const struct TALER_BlindedCoinHashP *h_coin_ev,
const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
const struct TALER_CoinSpendSignatureP *coin_sig);
/**
- * Sign link data.
+ * Sign withdraw request.
*
- * @param h_denom_pub hash of the denomiantion public key of the new coin
- * @param transfer_pub transfer public key
- * @param coin_ev coin envelope
- * @param coin_ev_size number of bytes in @a coin_ev
- * @param old_coin_priv private key to sign with
- * @param[out] coin_sig resulting signature
+ * @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_link_sign (const struct TALER_DenominationHash *h_denom_pub,
- const struct TALER_TransferPublicKeyP *transfer_pub,
- const void *coin_ev,
- size_t coin_ev_size,
- const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
- struct TALER_CoinSpendSignatureP *coin_sig);
+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);
/**
@@ -1845,8 +3628,8 @@ TALER_wallet_link_sign (const struct TALER_DenominationHash *h_denom_pub,
*/
enum GNUNET_GenericReturnValue
TALER_wallet_recoup_verify (
- const struct TALER_DenominationHash *h_denom_pub,
- const union TALER_DenominationBlindingKeyP *coin_bks,
+ 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);
@@ -1856,14 +3639,13 @@ TALER_wallet_recoup_verify (
*
* @param h_denom_pub hash of the denomiantion public key of the coin
* @param coin_bks blinding factor used when withdrawing the coin
- * @param requested_amount amount that is left to be recouped
* @param coin_priv coin key of the coin to be recouped
- * @param coin_sig resulting signature
+ * @param[out] coin_sig resulting signature
*/
void
TALER_wallet_recoup_sign (
- const struct TALER_DenominationHash *h_denom_pub,
- const union TALER_DenominationBlindingKeyP *coin_bks,
+ 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);
@@ -1879,8 +3661,8 @@ TALER_wallet_recoup_sign (
*/
enum GNUNET_GenericReturnValue
TALER_wallet_recoup_refresh_verify (
- const struct TALER_DenominationHash *h_denom_pub,
- const union TALER_DenominationBlindingKeyP *coin_bks,
+ 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);
@@ -1890,14 +3672,71 @@ TALER_wallet_recoup_refresh_verify (
*
* @param h_denom_pub hash of the denomiantion public key of the coin
* @param coin_bks blinding factor used when withdrawing the coin
- * @param requested_amount amount that is left to be recouped
* @param coin_priv coin key of the coin to be recouped
- * @param coin_sig resulting signature
+ * @param[out] coin_sig resulting signature
*/
void
TALER_wallet_recoup_refresh_sign (
- const struct TALER_DenominationHash *h_denom_pub,
- const union TALER_DenominationBlindingKeyP *coin_bks,
+ 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);
@@ -1918,7 +3757,7 @@ TALER_wallet_recoup_refresh_sign (
void
TALER_merchant_refund_sign (
const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_PrivateContractHash *h_contract_terms,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
uint64_t rtransaction_id,
const struct TALER_Amount *amount,
const struct TALER_MerchantPrivateKeyP *merchant_priv,
@@ -1939,17 +3778,1001 @@ TALER_merchant_refund_sign (
enum GNUNET_GenericReturnValue
TALER_merchant_refund_verify (
const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_PrivateContractHash *h_contract_terms,
+ 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
@@ -2028,7 +4851,7 @@ TALER_exchange_offline_auditor_del_verify (
*/
void
TALER_exchange_offline_denomination_revoke_sign (
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_MasterPrivateKeyP *master_priv,
struct TALER_MasterSignatureP *master_sig);
@@ -2043,7 +4866,7 @@ TALER_exchange_offline_denomination_revoke_sign (
*/
enum GNUNET_GenericReturnValue
TALER_exchange_offline_denomination_revoke_verify (
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_MasterPublicKeyP *master_pub,
const struct TALER_MasterSignatureP *master_sig);
@@ -2127,25 +4950,19 @@ TALER_exchange_offline_signkey_validity_verify (
* @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 fee_withdraw what withdraw fee does the exchange charge for this denomination
- * @param fee_deposit what deposit fee does the exchange charge for this denomination
- * @param fee_refresh what refresh fee does the exchange charge for this denomination
- * @param fee_refund what refund fee does the exchange charge for this denomination
+ * @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_DenominationHash *h_denom_pub,
+ 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_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,
const struct TALER_MasterPrivateKeyP *master_priv,
struct TALER_MasterSignatureP *master_sig);
@@ -2159,26 +4976,118 @@ TALER_exchange_offline_denom_validity_sign (
* @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 fee_withdraw what withdraw fee does the exchange charge for this denomination
- * @param fee_deposit what deposit fee does the exchange charge for this denomination
- * @param fee_refresh what refresh fee does the exchange charge for this denomination
- * @param fee_refund what refund fee does the exchange charge for this denomination
+ * @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_DenominationHash *h_denom_pub,
+ 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_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,
+ 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);
@@ -2262,6 +5171,47 @@ TALER_exchange_secmod_rsa_verify (
/**
+ * 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
@@ -2272,27 +5222,21 @@ TALER_exchange_secmod_rsa_verify (
* @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 fee_withdraw what withdraw fee does the exchange charge for this denomination
- * @param fee_deposit what deposit fee does the exchange charge for this denomination
- * @param fee_refresh what refresh fee does the exchange charge for this denomination
- * @param fee_refund what refund fee does the exchange charge for this denomination
+ * @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_DenominationHash *h_denom_pub,
+ 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_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,
const struct TALER_AuditorPrivateKeyP *auditor_priv,
struct TALER_AuditorSignatureP *auditor_sig);
@@ -2308,10 +5252,7 @@ TALER_auditor_denom_validity_sign (
* @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 fee_withdraw what withdraw fee does the exchange charge for this denomination
- * @param fee_deposit what deposit fee does the exchange charge for this denomination
- * @param fee_refresh what refresh fee does the exchange charge for this denomination
- * @param fee_refund what refund fee does the exchange charge for this denomination
+ * @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
@@ -2319,17 +5260,14 @@ TALER_auditor_denom_validity_sign (
enum GNUNET_GenericReturnValue
TALER_auditor_denom_validity_verify (
const char *auditor_url,
- const struct TALER_DenominationHash *h_denom_pub,
+ 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_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,
const struct TALER_AuditorPublicKeyP *auditor_pub,
const struct TALER_AuditorSignatureP *auditor_sig);
@@ -2343,8 +5281,7 @@ TALER_auditor_denom_validity_verify (
* @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 wire_fee the wire fee
- * @param closing_fee the closing fee
+ * @param fees the wire fees
* @param master_priv private key to sign with
* @param[out] master_sig where to write the signature
*/
@@ -2353,8 +5290,7 @@ 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_Amount *wire_fee,
- const struct TALER_Amount *closing_fee,
+ const struct TALER_WireFeeSet *fees,
const struct TALER_MasterPrivateKeyP *master_priv,
struct TALER_MasterSignatureP *master_sig);
@@ -2365,8 +5301,7 @@ TALER_exchange_offline_wire_fee_sign (
* @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 wire_fee the wire fee
- * @param closing_fee the closing fee
+ * @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
@@ -2376,8 +5311,56 @@ 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_Amount *wire_fee,
- const struct TALER_Amount *closing_fee,
+ 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);
@@ -2386,6 +5369,9 @@ TALER_exchange_offline_wire_fee_verify (
* Create 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 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
@@ -2393,6 +5379,9 @@ TALER_exchange_offline_wire_fee_verify (
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);
@@ -2402,6 +5391,9 @@ TALER_exchange_offline_wire_add_sign (
* 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
@@ -2410,6 +5402,9 @@ TALER_exchange_offline_wire_add_sign (
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);
@@ -2452,6 +5447,9 @@ TALER_exchange_offline_wire_del_verify (
* Check the signature in @a master_sig.
*
* @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
@@ -2459,6 +5457,9 @@ TALER_exchange_offline_wire_del_verify (
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);
@@ -2467,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);
@@ -2488,8 +5495,8 @@ TALER_exchange_wire_signature_make (
*/
void
TALER_merchant_wire_signature_hash (const char *payto_uri,
- const struct TALER_WireSalt *salt,
- struct TALER_MerchantWireHash *hc);
+ const struct TALER_WireSaltP *salt,
+ struct TALER_MerchantWireHashP *hc);
/**
@@ -2504,7 +5511,7 @@ TALER_merchant_wire_signature_hash (const char *payto_uri,
enum GNUNET_GenericReturnValue
TALER_merchant_wire_signature_check (
const char *payto_uri,
- const struct TALER_WireSalt *salt,
+ const struct TALER_WireSaltP *salt,
const struct TALER_MerchantPublicKeyP *merch_pub,
const struct TALER_MerchantSignatureP *merch_sig);
@@ -2520,42 +5527,444 @@ TALER_merchant_wire_signature_check (
void
TALER_merchant_wire_signature_make (
const char *payto_uri,
- const struct TALER_WireSalt *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 configuration of an extension
+ * Create a signature for the hash of the manifests of extensions
*
- * @param h_config hash of the JSON object representing the configuration
+ * @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_config_hash_sign (
- const struct TALER_ExtensionConfigHash h_config,
+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 configuration of an extension
+ * blob representing the manifests of extensions
*
- * @param h_config hash of the JSON blob of a configuration of an extension
+ * @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_config_hash_verify (
- const struct TALER_ExtensionConfigHash h_config,
+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 5151f4cf6..f108e6158 100644
--- a/src/include/taler_curl_lib.h
+++ b/src/include/taler_curl_lib.h
@@ -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;
};
@@ -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_exchange_service.h b/src/include/taler_exchange_service.h
index 7fb5b4ec0..0597799b5 100644
--- a/src/include/taler_exchange_service.h
+++ b/src/include/taler_exchange_service.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 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
@@ -16,8 +16,11 @@
/**
* @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 *********************** */
/**
@@ -97,7 +86,7 @@ struct TALER_EXCHANGE_DenomPublicKey
/**
* The hash of the public key.
*/
- struct TALER_DenominationHash h_key;
+ struct TALER_DenominationHashP h_key;
/**
* Exchange's master signature over this denomination record.
@@ -135,24 +124,16 @@ 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 true if this denomination key has been
@@ -160,10 +141,6 @@ struct TALER_EXCHANGE_DenomPublicKey
*/
bool revoked;
- /**
- * Is the denomination age-restricted?
- */
- bool age_restricted;
};
@@ -224,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
@@ -235,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;
@@ -250,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
@@ -263,6 +476,41 @@ struct TALER_EXCHANGE_Keys
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).
@@ -270,15 +518,14 @@ struct TALER_EXCHANGE_Keys
struct GNUNET_TIME_Relative reserve_closing_delay;
/**
- * Maximum amount a wallet is allowed to hold from
- * this exchange before it must undergo a KYC check.
+ * Timestamp indicating the /keys generation.
*/
- struct TALER_Amount wallet_balance_limit_without_kyc;
+ struct GNUNET_TIME_Timestamp list_issue_date;
/**
- * Timestamp indicating the /keys generation.
+ * When does this keys data expire?
*/
- struct GNUNET_TIME_Timestamp list_issue_date;
+ struct GNUNET_TIME_Timestamp key_data_expiration;
/**
* Timestamp indicating the creation time of the last
@@ -293,6 +540,42 @@ struct TALER_EXCHANGE_Keys
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).
*/
unsigned int num_sign_keys;
@@ -317,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;
};
@@ -413,151 +706,195 @@ struct TALER_EXCHANGE_HttpResponse
/**
+ * 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 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 hr HTTP response data
- * @param keys information about the various keys used
- * by the exchange, NULL if /keys failed
- * @param compat protocol compatibility information
+ * @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_HttpResponse *hr,
- const struct TALER_EXCHANGE_Keys *keys,
- enum TALER_EXCHANGE_VersionCompatibility compat);
+ 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);
/**
- * Let the user set the last valid denomination time manually.
+ * Increment reference counter for @a keys
*
- * @param exchange the exchange handle.
- * @param last_denom_new new last denomination time.
+ * @param[in,out] keys object to increment reference counter for
+ * @return keys, with incremented reference counter
*/
-void
-TALER_EXCHANGE_set_last_denom (struct TALER_EXCHANGE_Handle *exchange,
- struct GNUNET_TIME_Timestamp last_denom_new);
+struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_keys_incref (struct TALER_EXCHANGE_Keys *keys);
/**
- * Flags for #TALER_EXCHANGE_check_keys_current().
+ * Decrement reference counter for @a keys.
+ * Frees @a keys if reference counter becomes zero.
+ *
+ * @param[in,out] keys object to decrement reference counter for
*/
-enum TALER_EXCHANGE_CheckKeysFlags
-{
- /**
- * No special options.
- */
- TALER_EXCHANGE_CKF_NONE,
-
- /**
- * Force downloading /keys now, even if /keys is still valid
- * (that is, the period advertised by the exchange for re-downloads
- * has not yet expired).
- */
- TALER_EXCHANGE_CKF_FORCE_DOWNLOAD = 1,
-
- /**
- * Pull all keys again, resetting the client state to the original state.
- * Using this flag disables the incremental download, and also prevents using
- * the context until the re-download has completed.
- */
- TALER_EXCHANGE_CKF_PULL_ALL_KEYS = 2,
+void
+TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys);
- /**
- * Force downloading all keys now.
- */
- TALER_EXCHANGE_CKF_FORCE_ALL_NOW = TALER_EXCHANGE_CKF_FORCE_DOWNLOAD
- | TALER_EXCHANGE_CKF_PULL_ALL_KEYS
-};
+/**
+ * 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 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)
+ */
+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 flags options controlling when to download what
- * @return until when the existing response is current, 0 if we are re-downloading now
+ * @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_Timestamp
-TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange,
- enum TALER_EXCHANGE_CheckKeysFlags flags);
+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);
/**
@@ -569,18 +906,9 @@ TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange);
* @return #GNUNET_OK if @a pub is (according to /keys) a current signing key
*/
enum GNUNET_GenericReturnValue
-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);
+TALER_EXCHANGE_test_signing_key (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ExchangePublicKeyP *pub);
/**
@@ -598,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 (
@@ -610,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 (
@@ -629,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 TALER_DenominationHash *hc);
+ const struct TALER_DenominationHashP *hc);
/**
@@ -646,176 +989,156 @@ 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_Timestamp 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_Timestamp 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.
*/
- const struct TALER_EXCHANGE_WireAggregateFees *fees;
-
-};
-
-
-/**
- * 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 hr HTTP response data
- * @param accounts_len length of the @a accounts array
- * @param accounts list of wire accounts of the exchange, NULL on error
- */
-typedef void
-(*TALER_EXCHANGE_WireCallback) (
- void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- unsigned int accounts_len,
- const struct TALER_EXCHANGE_WireAccount *accounts);
-
-
-/**
- * @brief A Wire format inquiry handle
- */
-struct TALER_EXCHANGE_WireHandle;
-
+ struct TALER_WireSaltP wire_salt;
-/**
- * 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);
+ /**
+ * 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;
-/**
- * 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);
+ /**
+ * 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;
-/* ********************* /coins/$COIN_PUB/deposit *********************** */
+ /**
+ * 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 json_t *policy_details;
-/**
- * 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 h_extensions hash over the extensions
- * @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_EXCHANGE_deposit_permission_sign (
- const struct TALER_Amount *amount,
- const struct TALER_Amount *deposit_fee,
- const struct TALER_MerchantWireHash *h_wire,
- const struct TALER_PrivateContractHash *h_contract_terms,
- const struct TALER_ExtensionContractHash *h_extensions,
- const struct TALER_DenominationHash *h_denom_pub,
- const struct TALER_CoinSpendPrivateKeyP *coin_priv,
- struct GNUNET_TIME_Timestamp wallet_timestamp,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- struct GNUNET_TIME_Timestamp refund_deadline,
- struct TALER_CoinSpendSignatureP *coin_sig);
+};
/**
- * @brief A Deposit Handle
+ * @brief A Batch Deposit Handle
*/
-struct TALER_EXCHANGE_DepositHandle;
+struct TALER_EXCHANGE_BatchDepositHandle;
/**
- * Structure with information about a deposit
+ * Structure with information about a batch deposit
* operation's result.
*/
-struct TALER_EXCHANGE_DepositResult
+struct TALER_EXCHANGE_BatchDepositResult
{
/**
* HTTP response data
@@ -832,12 +1155,12 @@ struct TALER_EXCHANGE_DepositResult
struct
{
/**
- * Time when the exchange generated the deposit confirmation
+ * Time when the exchange generated the batch deposit confirmation
*/
struct GNUNET_TIME_Timestamp deposit_timestamp;
/**
- * signature provided by the exchange
+ * Deposit confirmation signature provided by the exchange
*/
const struct TALER_ExchangeSignatureP *exchange_sig;
@@ -852,7 +1175,7 @@ struct TALER_EXCHANGE_DepositResult
*/
const char *transaction_base_url;
- } success;
+ } ok;
/**
* Information returned if the HTTP status is
@@ -860,7 +1183,11 @@ struct TALER_EXCHANGE_DepositResult
*/
struct
{
- /* TODO: returning full details is not implemented */
+ /**
+ * The coin that had a conflict.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
} conflict;
} details;
@@ -875,64 +1202,46 @@ struct TALER_EXCHANGE_DepositResult
* @param dr deposit response details
*/
typedef void
-(*TALER_EXCHANGE_DepositResultCallback) (
+(*TALER_EXCHANGE_BatchDepositResultCallback) (
void *cls,
- const struct TALER_EXCHANGE_DepositResult *dr);
+ 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 merchant_payto_uri the merchant’s account details, in the payto://-format supported by the exchange
- * @param wire_salt salt used to hash the @a merchant_payto_uri
- * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange)
- * @param extension_details extension-specific details about the deposit relevant 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_Timestamp wire_deadline,
- const char *merchant_payto_uri,
- const struct TALER_WireSalt *wire_salt,
- const struct TALER_PrivateContractHash *h_contract_terms,
- const json_t *extension_details,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_DenominationPublicKey *denom_pub,
- struct GNUNET_TIME_Timestamp timestamp,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- struct GNUNET_TIME_Timestamp refund_deadline,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- TALER_EXCHANGE_DepositResultCallback cb,
+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);
@@ -941,20 +1250,22 @@ TALER_EXCHANGE_deposit (
* 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 *********************** */
@@ -964,23 +1275,51 @@ 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 hr HTTP response data
- * @param sign_key exchange key used to sign @a obj, or NULL
- * @param signature the actual signature, or NULL on error
+ * @param rr refund response
*/
typedef void
(*TALER_EXCHANGE_RefundCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_ExchangePublicKeyP *sign_key,
- const struct TALER_ExchangeSignatureP *signature);
-
+ const struct TALER_EXCHANGE_RefundResponse *rr);
/**
* Submit a refund request to the exchange and get the exchange's response.
@@ -994,7 +1333,9 @@ typedef void
* 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
@@ -1011,14 +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_PrivateContractHash *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);
/**
@@ -1033,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.
@@ -1059,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,
@@ -1066,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
};
@@ -1074,7 +1971,7 @@ enum TALER_EXCHANGE_ReserveTransactionType
/**
* @brief Entry in the reserve's transaction history.
*/
-struct TALER_EXCHANGE_ReserveHistory
+struct TALER_EXCHANGE_ReserveHistoryEntry
{
/**
@@ -1134,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.
*/
@@ -1170,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;
@@ -1202,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;
};
@@ -1212,18 +2298,12 @@ struct TALER_EXCHANGE_ReserveHistory
* reserve status request to a exchange.
*
* @param cls closure
- * @param hr HTTP response data
- * @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,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_Amount *balance,
- unsigned int history_length,
- const struct TALER_EXCHANGE_ReserveHistory *history);
+ const struct TALER_EXCHANGE_ReserveSummary *rs);
/**
@@ -1235,7 +2315,8 @@ 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)
@@ -1246,7 +2327,8 @@ 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,
@@ -1264,19 +2346,187 @@ 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;
+
+};
/**
- * Details about a response for a withdraw request.
+ * Callbacks of this type are used to serve the result of submitting a
+ * reserve history request to a exchange.
+ *
+ * @param cls closure
+ * @param rs HTTP response data
*/
-struct TALER_EXCHANGE_WithdrawResponse
+typedef void
+(*TALER_EXCHANGE_ReservesHistoryCallback) (
+ void *cls,
+ const struct TALER_EXCHANGE_ReserveHistory *rs);
+
+
+/**
+ * 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.
@@ -1293,23 +2543,35 @@ struct TALER_EXCHANGE_WithdrawResponse
*/
struct
{
+
+ /**
+ * Array of coins returned by the batch withdraw operation.
+ */
+ struct TALER_EXCHANGE_PrivateCoinDetails *coins;
+
/**
- * Signature over the coin.
+ * Length of the @e coins array.
*/
- struct TALER_DenominationSignature sig;
- } success;
+ unsigned int num_coins;
+ } ok;
/**
- * Details if the status is #MHD_HTTP_ACCEPTED.
+ * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
*/
struct
{
+
/**
- * Payment target that the merchant should use
- * to check for its KYC status.
+ * Hash of the payto-URI of the account to KYC;
*/
- uint64_t payment_target_uuid;
- } accepted;
+ 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.
@@ -1333,71 +2595,103 @@ struct TALER_EXCHANGE_WithdrawResponse
/**
* Callbacks of this type are used to serve the result of submitting a
- * withdraw request to a exchange.
+ * batch withdraw request to a exchange.
*
* @param cls closure
* @param wr response details
*/
typedef void
-(*TALER_EXCHANGE_WithdrawCallback) (
+(*TALER_EXCHANGE_BatchWithdrawCallback) (
void *cls,
- const struct TALER_EXCHANGE_WithdrawResponse *wr);
+ const struct TALER_EXCHANGE_BatchWithdrawResponse *wr);
/**
- * 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.
+ * 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 withdraw status request. This function cannot be used
- * on a request handle if a response is already served for it.
+ * 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 withdraw handle
+ * @param wh the batch withdraw handle
*/
void
-TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh);
+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 hr HTTP response data
- * @param blind_sig blind signature over the coin, NULL on error
+ * @param w2r response data
*/
typedef void
(*TALER_EXCHANGE_Withdraw2Callback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_BlindedDenominationSignature *blind_sig);
+ const struct TALER_EXCHANGE_Withdraw2Response *w2r);
/**
@@ -1413,14 +2707,20 @@ struct TALER_EXCHANGE_Withdraw2Handle;
/**
* Withdraw a coin from the exchange using a /reserves/$RESERVE_PUB/withdraw
* request. This API is typically used by a merchant to withdraw a tip
- * where the blinding factor is unknown to the merchant.
+ * 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 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
@@ -1430,11 +2730,14 @@ struct TALER_EXCHANGE_Withdraw2Handle;
* In this case, the callback is not called.
*/
struct TALER_EXCHANGE_Withdraw2Handle *
-TALER_EXCHANGE_withdraw2 (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_PlanchetDetail *pd,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- TALER_EXCHANGE_Withdraw2Callback res_cb,
- void *res_cb_cls);
+TALER_EXCHANGE_withdraw2 (
+ 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);
/**
@@ -1447,54 +2750,592 @@ void
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
+ * 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, JSON data structure 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.
*/
-json_t *
-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);
+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
+ */
+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 ***************************** */
@@ -1506,23 +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 hr HTTP response data
- * @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 mr response details
*/
typedef void
(*TALER_EXCHANGE_MeltCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- uint32_t noreveal_index,
- const struct TALER_ExchangePublicKeyP *sign_key);
+ const struct TALER_EXCHANGE_MeltResponse *mr);
/**
@@ -1531,23 +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 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,
- const json_t *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);
/**
@@ -1564,26 +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 hr HTTP response data
- * @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 rr result of the reveal operation
*/
typedef void
(*TALER_EXCHANGE_RefreshesRevealCallback)(
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- unsigned int num_coins,
- const struct TALER_PlanchetSecretsP *coin_privs,
- const struct TALER_DenominationSignature *sigs);
+ const struct TALER_EXCHANGE_RevealResult *rr);
/**
@@ -1601,9 +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 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
@@ -1614,8 +3585,12 @@ struct TALER_EXCHANGE_RefreshesRevealHandle;
*/
struct TALER_EXCHANGE_RefreshesRevealHandle *
TALER_EXCHANGE_refreshes_reveal (
- struct TALER_EXCHANGE_Handle *exchange,
- const json_t *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);
@@ -1642,26 +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 hr HTTP response data
- * @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 lr result of the /link operation
*/
typedef void
(*TALER_EXCHANGE_LinkCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- unsigned int num_coins,
- const struct TALER_CoinSpendPrivateKeyP *coin_privs,
- const struct TALER_DenominationSignature *sigs,
- const struct TALER_DenominationPublicKey *pubs);
+ const struct TALER_EXCHANGE_LinkResult *lr);
/**
@@ -1670,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);
/**
@@ -1721,7 +3765,7 @@ struct TALER_EXCHANGE_TransferData
/**
* hash of the payto:// URI the transfer went to
*/
- struct TALER_PaytoHash h_payto;
+ struct TALER_PaytoHashP h_payto;
/**
* time when the exchange claims to have performed the wire transfer
@@ -1752,25 +3796,52 @@ struct TALER_EXCHANGE_TransferData
/**
+ * 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 hr HTTP response data
- * @param ta transfer data, (set only if @a http_status is #MHD_HTTP_OK, otherwise NULL)
+ * @param tgr response data
*/
typedef void
(*TALER_EXCHANGE_TransfersGetCallback)(
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_EXCHANGE_TransferData *ta);
+ 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
@@ -1778,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);
@@ -1855,12 +3928,7 @@ struct TALER_EXCHANGE_GetDepositResponse
*/
struct TALER_Amount coin_contribution;
- /**
- * Payment target that the merchant should use
- * to check for its KYC status.
- */
- uint64_t payment_target_uuid;
- } success;
+ } ok;
/**
* Response if the status was #MHD_HTTP_ACCEPTED
@@ -1874,10 +3942,16 @@ struct TALER_EXCHANGE_GetDepositResponse
struct GNUNET_TIME_Timestamp execution_time;
/**
- * Payment target that the merchant should use
- * to check for its KYC status.
+ * 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.
*/
- uint64_t payment_target_uuid;
+ enum TALER_AmlDecisionState aml_decision;
/**
* Set to 'true' if the KYC check is already finished and
@@ -1894,8 +3968,7 @@ struct TALER_EXCHANGE_GetDepositResponse
* Function called with detailed wire transfer data.
*
* @param cls closure
- * @param hr HTTP response data
- * @param dd details about the deposit (NULL on errors)
+ * @param dr details about the deposit response
*/
typedef void
(*TALER_EXCHANGE_DepositGetCallback)(
@@ -1908,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 TALER_MerchantWireHash *h_wire,
- const struct TALER_PrivateContractHash *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);
@@ -1939,74 +4018,44 @@ TALER_EXCHANGE_deposits_get_cancel (
struct TALER_EXCHANGE_DepositGetHandle *dwh);
-/**
- * Convenience function. Verifies a coin's 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] h_denom_pub set to the hash of the coin's denomination (if available)
- * @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
- */
-enum GNUNET_GenericReturnValue
-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_DenominationHash *h_denom_pub,
- struct TALER_Amount *total);
+/* ********************* /recoup *********************** */
/**
- * 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
+ * @brief A /recoup Handle
*/
-enum GNUNET_GenericReturnValue
-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);
+struct TALER_EXCHANGE_RecoupHandle;
/**
- * Free memory (potentially) allocated by #TALER_EXCHANGE_parse_reserve_history().
- *
- * @param rhistory result to free
- * @param len number of entries in @a rhistory
+ * Response from a recoup request.
*/
-void
-TALER_EXCHANGE_free_reserve_history (
- struct TALER_EXCHANGE_ReserveHistory *rhistory,
- unsigned int len);
-
+struct TALER_EXCHANGE_RecoupResponse
+{
+ /**
+ * HTTP response data
+ */
+ struct TALER_EXCHANGE_HttpResponse hr;
-/* ********************* /recoup *********************** */
+ /**
+ * 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;
-/**
- * @brief A /recoup Handle
- */
-struct TALER_EXCHANGE_RecoupHandle;
+};
/**
@@ -2016,14 +4065,12 @@ struct TALER_EXCHANGE_RecoupHandle;
* reserve that was credited.
*
* @param cls closure
- * @param hr HTTP response data
- * @param reserve_pub public key of the reserve receiving the recoup
+ * @param rr response data
*/
typedef void
(*TALER_EXCHANGE_RecoupResultCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_ReservePublicKeyP *reserve_pub);
+ const struct TALER_EXCHANGE_RecoupResponse *rr);
/**
@@ -2031,9 +4078,12 @@ typedef void
* 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 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
@@ -2042,12 +4092,16 @@ typedef void
* 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,
- 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);
/**
@@ -2070,18 +4124,47 @@ 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 recoup-refresh request to a exchange.
*
* @param cls closure
- * @param hr HTTP response data
- * @param old_coin_pub public key of the dirty coin that was credited
+ * @param rrr response data
*/
typedef void
(*TALER_EXCHANGE_RecoupRefreshResultCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_CoinSpendPublicKeyP *old_coin_pub);
+ const struct TALER_EXCHANGE_RecoupRefreshResponse *rrr);
/**
@@ -2091,10 +4174,15 @@ typedef void
* 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 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
@@ -2103,10 +4191,15 @@ typedef void
*/
struct TALER_EXCHANGE_RecoupRefreshHandle *
TALER_EXCHANGE_recoup_refresh (
- struct TALER_EXCHANGE_Handle *exchange,
+ 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_PlanchetSecretsP *ps,
+ 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);
@@ -2155,6 +4248,11 @@ struct TALER_EXCHANGE_KycStatus
{
/**
+ * Details about which KYC check(s) were passed.
+ */
+ const json_t *kyc_details;
+
+ /**
* Time of the affirmation.
*/
struct GNUNET_TIME_Timestamp timestamp;
@@ -2166,19 +4264,50 @@ struct TALER_EXCHANGE_KycStatus
/**
* Signature of purpose
- * #TALER_SIGNATURE_ACCOUNT_SETUP_SUCCESS affirming
+ * #TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS affirming
* the successful KYC process.
*/
struct TALER_ExchangeSignatureP exchange_sig;
- } kyc_ok;
+ /**
+ * 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;
/**
- * 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.
+ * KYC is OK, but account needs positive AML decision.
*/
- const char *kyc_url;
+ struct
+ {
+
+ /**
+ * AML status for the account.
+ */
+ enum TALER_AmlDecisionState aml_status;
+
+ } unavailable_for_legal_reasons;
} details;
@@ -2200,21 +4329,28 @@ typedef void
* Run interaction with exchange to check KYC status
* of a merchant.
*
- * @param eh exchange handle to use
- * @param payment_target number identifying the target
+ * @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 TALER_EXCHANGE_Handle *eh,
- uint64_t payment_target,
- const struct TALER_PaytoHash *h_payto,
- struct GNUNET_TIME_Relative timeout,
- TALER_EXCHANGE_KycStatusCallback cb,
- void *cb_cls);
+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);
/**
@@ -2260,7 +4396,7 @@ struct TALER_EXCHANGE_KycProofResponse
* Function called with the result of a KYC check.
*
* @param cls closure
- * @param ks the account's KYC status details
+ * @param kpr the account's KYC status details
*/
typedef void
(*TALER_EXCHANGE_KycProofCallback)(
@@ -2277,21 +4413,25 @@ struct TALER_EXCHANGE_KycProofHandle;
/**
* Run interaction with exchange to provide proof of KYC status.
*
- * @param eh exchange handle to use
- * @param payment_target number identifying the target
- * @param code OAuth 2.0 code argument
- * @param state OAuth 2.0 state argument
+ * @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 TALER_EXCHANGE_Handle *eh,
- uint64_t payment_target,
- const char *code,
- const char *state,
- TALER_EXCHANGE_KycProofCallback cb,
- void *cb_cls);
+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);
/**
@@ -2326,10 +4466,29 @@ struct TALER_EXCHANGE_WalletKycResponse
enum TALER_ErrorCode ec;
/**
- * Wallet's payment target UUID. Only valid if
- * @e http_status is #MHD_HTTP_OK
+ * Variants depending on @e http_status.
*/
- uint64_t payment_target_uuid;
+ 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;
};
@@ -2350,17 +4509,22 @@ typedef void
* Run interaction with exchange to find out the wallet's KYC
* identifier.
*
- * @param eh exchange handle to use
+ * @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 TALER_EXCHANGE_Handle *eh,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- TALER_EXCHANGE_KycWalletCallback cb,
- void *cb_cls);
+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);
/**
@@ -2497,11 +4661,16 @@ struct TALER_EXCHANGE_FutureKeys
struct TALER_SecurityModulePublicKeyP signkey_secmod_public_key;
/**
- * Public key of the denomination security module.
+ * 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;
@@ -2520,18 +4689,47 @@ struct TALER_EXCHANGE_FutureKeys
/**
+ * 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 hr HTTP response data
- * @param keys information about the various keys used
- * by the exchange, NULL if /management/keys failed
+ * @param mgr HTTP response data
*/
typedef void
(*TALER_EXCHANGE_ManagementGetKeysCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_EXCHANGE_FutureKeys *keys);
+ const struct TALER_EXCHANGE_ManagementGetKeysResponse *mgr);
/**
@@ -2551,10 +4749,11 @@ struct TALER_EXCHANGE_ManagementGetKeysHandle;
* @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);
+TALER_EXCHANGE_get_management_keys (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ TALER_EXCHANGE_ManagementGetKeysCallback cb,
+ void *cb_cls);
/**
@@ -2594,7 +4793,7 @@ struct TALER_EXCHANGE_DenominationKeySignature
/**
* The hash of the denomination's public key
*/
- struct TALER_DenominationHash h_denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
/**
* Signature over this denomination key by the exchange's master signature.
@@ -2634,15 +4833,28 @@ struct TALER_EXCHANGE_ManagementPostKeysData
/**
+ * 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 hr HTTP response data
+ * @param mr response data
*/
typedef void
(*TALER_EXCHANGE_ManagementPostKeysCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr);
+ const struct TALER_EXCHANGE_ManagementPostKeysResponse *mr);
/**
@@ -2682,14 +4894,30 @@ TALER_EXCHANGE_post_management_keys_cancel (
/**
* 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
{
- struct TALER_Extension *extensions;
- struct TALER_MasterSignatureP *extensions_sigs;
- uint32_t num_extensions;
+ 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.
*
@@ -2699,7 +4927,7 @@ struct TALER_EXCHANGE_ManagementPostExtensionsData
typedef void
(*TALER_EXCHANGE_ManagementPostExtensionsCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr);
+ const struct TALER_EXCHANGE_ManagementPostExtensionsResponse *hr);
/**
* @brief Handle for a POST /management/extensions request.
@@ -2708,11 +4936,12 @@ struct TALER_EXCHANGE_ManagementPostExtensionsHandle;
/**
- * FIXME-oec: Provide correct explanation of this function.
+ * 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 pkd signature data to POST
+ * @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
@@ -2721,21 +4950,105 @@ struct TALER_EXCHANGE_ManagementPostExtensionsHandle *
TALER_EXCHANGE_management_post_extensions (
struct GNUNET_CURL_Context *ctx,
const char *url,
- const struct TALER_EXCHANGE_ManagementPostExtensionsData *pkd,
+ const struct TALER_EXCHANGE_ManagementPostExtensionsData *ped,
TALER_EXCHANGE_ManagementPostExtensionsCallback cb,
void *cb_cls);
+
/**
- * Cancel #TALER_EXCHANGE_post_management_extensions() operation.
+ * Cancel #TALER_EXCHANGE_management_post_extensions() operation.
*
* @param ph handle of the operation to cancel
*/
void
-TALER_EXCHANGE_post_management_extensions_cancel (
+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
@@ -2744,7 +5057,7 @@ TALER_EXCHANGE_post_management_extensions_cancel (
typedef void
(*TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr);
+ const struct TALER_EXCHANGE_ManagementRevokeDenominationResponse *hr);
/**
@@ -2768,7 +5081,7 @@ struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *
TALER_EXCHANGE_management_revoke_denomination_key (
struct GNUNET_CURL_Context *ctx,
const char *url,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_MasterSignatureP *master_sig,
TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback cb,
void *cb_cls);
@@ -2785,6 +5098,18 @@ TALER_EXCHANGE_management_revoke_denomination_key_cancel (
/**
+ * 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
@@ -2793,7 +5118,7 @@ TALER_EXCHANGE_management_revoke_denomination_key_cancel (
typedef void
(*TALER_EXCHANGE_ManagementRevokeSigningKeyCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr);
+ const struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse *hr);
/**
@@ -2834,15 +5159,515 @@ TALER_EXCHANGE_management_revoke_signing_key_cancel (
/**
- * Function called with information about the auditor setup operation result.
+ * 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_HttpResponse *hr);
+ const struct TALER_EXCHANGE_ManagementAuditorEnableResponse *aer);
/**
@@ -2887,17 +5712,27 @@ 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 hr HTTP response data
+ * @param adr HTTP response data
*/
typedef void
(*TALER_EXCHANGE_ManagementAuditorDisableCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr);
+ const struct TALER_EXCHANGE_ManagementAuditorDisableResponse *adr);
/**
@@ -2940,15 +5775,27 @@ TALER_EXCHANGE_management_disable_auditor_cancel (
/**
+ * 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 hr HTTP response data
+ * @param wer HTTP response data
*/
typedef void
(*TALER_EXCHANGE_ManagementWireEnableCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr);
+ const struct TALER_EXCHANGE_ManagementWireEnableResponse *wer);
/**
@@ -2963,11 +5810,16 @@ struct TALER_EXCHANGE_ManagementWireEnableHandle;
* @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
@@ -2977,9 +5829,14 @@ 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);
@@ -2995,15 +5852,26 @@ TALER_EXCHANGE_management_enable_wire_cancel (
/**
+ * 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 hr HTTP response data
+ * @param wdr response data
*/
typedef void
(*TALER_EXCHANGE_ManagementWireDisableCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr);
+ const struct TALER_EXCHANGE_ManagementWireDisableResponse *wdr);
/**
@@ -3047,15 +5915,26 @@ TALER_EXCHANGE_management_disable_wire_cancel (
/**
+ * 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 hr HTTP response data
+ * @param wfr response data
*/
typedef void
(*TALER_EXCHANGE_ManagementSetWireFeeCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr);
+ const struct TALER_EXCHANGE_ManagementSetWireFeeResponse *wfr);
/**
@@ -3072,8 +5951,7 @@ struct TALER_EXCHANGE_ManagementSetWireFeeHandle;
* @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 wire_fee the wire fee for this time period
- * @param closing_fee the closing fee for this time period
+ * @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
@@ -3087,10 +5965,9 @@ TALER_EXCHANGE_management_set_wire_fees (
const char *wire_method,
struct GNUNET_TIME_Timestamp validity_start,
struct GNUNET_TIME_Timestamp validity_end,
- const struct TALER_Amount *wire_fee,
- const struct TALER_Amount *closing_fee,
+ const struct TALER_WireFeeSet *fees,
const struct TALER_MasterSignatureP *master_sig,
- TALER_EXCHANGE_ManagementWireEnableCallback cb,
+ TALER_EXCHANGE_ManagementSetWireFeeCallback cb,
void *cb_cls);
@@ -3105,16 +5982,100 @@ TALER_EXCHANGE_management_set_wire_fees_cancel (
/**
+ * 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 hr HTTP response data
+ * @param adr HTTP response data
*/
typedef void
(*TALER_EXCHANGE_AuditorAddDenominationCallback) (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr);
+ const struct TALER_EXCHANGE_AuditorAddDenominationResponse *adr);
/**
@@ -3140,7 +6101,7 @@ struct TALER_EXCHANGE_AuditorAddDenominationHandle *
TALER_EXCHANGE_add_auditor_denomination (
struct GNUNET_CURL_Context *ctx,
const char *url,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_AuditorPublicKeyP *auditor_pub,
const struct TALER_AuditorSignatureP *auditor_sig,
TALER_EXCHANGE_AuditorAddDenominationCallback cb,
@@ -3157,4 +6118,1210 @@ 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 7f466728a..d93cf9d6c 100644
--- a/src/include/taler_exchangedb_lib.h
+++ b/src/include/taler_exchangedb_lib.h
@@ -46,7 +46,6 @@ TALER_EXCHANGEDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg);
void
TALER_EXCHANGEDB_plugin_unload (struct TALER_EXCHANGEDB_Plugin *plugin);
-
/**
* Information about an account from the configuration.
*/
@@ -70,13 +69,13 @@ struct TALER_EXCHANGEDB_AccountInfo
const char *method;
/**
- * true if this account is enabed to be debited
+ * true if this account is enabled to be debited
* by the taler-exchange-aggregator.
*/
bool debit_enabled;
/**
- * true 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.
@@ -95,7 +94,7 @@ struct TALER_EXCHANGEDB_AccountInfo
* @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,
diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h
index 5eb168e14..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-2021 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.
*/
- struct TALER_ExchangePrivateKeyP signkey_priv;
+ uint64_t known_coin_id;
/**
- * Signature over @e issue
+ * Hash of the denomination, relevant on @e denom_conflict.
*/
- struct TALER_MasterSignatureP master_sig;
+ struct TALER_DenominationHashP denom_hash;
/**
- * Public information about a exchange signing key.
+ * Hash of the age commitment, relevant on @e age_conflict.
*/
- struct TALER_ExchangeSigningKeyValidityPS issue;
+ struct TALER_AgeCommitmentHash h_age_commitment;
+ /**
+ * True if the coin was known previously.
+ */
+ 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,91 @@ 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_DenominationKeyValidityPS properties;
+ struct TALER_AgeMask age_mask;
};
+GNUNET_NETWORK_STRUCT_BEGIN
+
/**
- * Signature of events signalling a reserve got funding.
+ * 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
{
@@ -91,6 +210,24 @@ struct TALER_ReserveEventP
/**
+ * 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_PurseContractPublicKeyP purse_pub;
+};
+
+
+/**
* Signature of events signalling a KYC process was completed.
*/
struct TALER_KycCompletedEventP
@@ -103,7 +240,7 @@ struct TALER_KycCompletedEventP
/**
* Public key of the reserve the event is about.
*/
- struct TALER_PaytoHash h_payto;
+ struct TALER_PaytoHashP h_payto;
};
@@ -142,12 +279,17 @@ struct TALER_EXCHANGEDB_SignkeyMetaData
*/
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,
@@ -157,13 +299,36 @@ enum TALER_EXCHANGEDB_ReplicatedTable
TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS,
TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS,
TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS,
- TALER_EXCHANGEDB_RT_DEPOSITS,
+ 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_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,
};
@@ -194,7 +359,7 @@ struct TALER_EXCHANGEDB_TableData
struct
{
uint32_t denom_type;
- uint32_t age_restrictions;
+ uint32_t age_mask;
struct TALER_DenominationPublicKey denom_pub;
struct TALER_MasterSignatureP master_sig;
struct GNUNET_TIME_Timestamp valid_from;
@@ -202,10 +367,7 @@ struct TALER_EXCHANGEDB_TableData
struct GNUNET_TIME_Timestamp expire_deposit;
struct GNUNET_TIME_Timestamp expire_legal;
struct TALER_Amount coin;
- struct TALER_Amount fee_withdraw;
- struct TALER_Amount fee_deposit;
- struct TALER_Amount fee_refresh;
- struct TALER_Amount fee_refund;
+ struct TALER_DenomFeeSet fees;
} denominations;
struct
@@ -217,17 +379,28 @@ struct TALER_EXCHANGEDB_TableData
struct
{
char *payto_uri;
- bool kyc_ok;
- char *external_id;
} 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;
- /**
- * Note: not useful for auditor, because not UPDATEd!
- */
- struct TALER_Amount current_balance;
struct GNUNET_TIME_Timestamp expiration_date;
struct GNUNET_TIME_Timestamp gc_date;
} reserves;
@@ -236,7 +409,7 @@ struct TALER_EXCHANGEDB_TableData
{
uint64_t wire_reference;
struct TALER_Amount credit;
- uint64_t sender_account;
+ struct TALER_PaytoHashP sender_account_h_payto;
char *exchange_account_section;
struct GNUNET_TIME_Timestamp execution_date;
struct TALER_ReservePublicKeyP reserve_pub;
@@ -245,16 +418,35 @@ struct TALER_EXCHANGEDB_TableData
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;
- uint64_t wire_target_serial_id;
+ struct TALER_PaytoHashP sender_account_h_payto;
struct TALER_Amount amount;
struct TALER_Amount closing_fee;
} reserves_close;
struct
{
- struct TALER_BlindedCoinHash h_blind_ev;
+ struct TALER_BlindedCoinHashP h_blind_ev;
uint64_t denominations_serial;
struct TALER_BlindedDenominationSignature denom_sig;
uint64_t reserve_uuid;
@@ -295,7 +487,7 @@ struct TALER_EXCHANGEDB_TableData
struct
{
struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_AgeHash age_hash;
+ struct TALER_AgeCommitmentHash age_hash;
uint64_t denominations_serial;
struct TALER_DenominationSignature denom_sig;
} known_coins;
@@ -317,6 +509,7 @@ struct TALER_EXCHANGEDB_TableData
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;
@@ -331,26 +524,33 @@ struct TALER_EXCHANGEDB_TableData
struct
{
uint64_t shard;
- uint64_t known_coin_id;
- struct TALER_Amount amount_with_fee;
+ 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_MerchantPublicKeyP merchant_pub;
- struct TALER_PrivateContractHash h_contract_terms;
+ 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_WireSalt wire_salt;
- uint64_t wire_target_serial_id;
- bool tiny;
- bool done;
- bool extension_blocked;
- uint64_t extension_details_serial_id;
- } deposits;
+ struct TALER_Amount amount_with_fee;
+ } coin_deposits;
struct
{
- uint64_t deposit_serial_id;
+ 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;
@@ -360,14 +560,14 @@ struct TALER_EXCHANGEDB_TableData
{
struct GNUNET_TIME_Timestamp execution_date;
struct TALER_WireTransferIdentifierRawP wtid_raw;
- uint64_t wire_target_serial_id;
+ struct TALER_PaytoHashP wire_target_h_payto;
char *exchange_account_section;
struct TALER_Amount amount;
} wire_out;
struct
{
- uint64_t deposit_serial_id;
+ uint64_t batch_deposit_serial_id;
struct TALER_WireTransferIdentifierRawP wtid_raw;
} aggregation_tracking;
@@ -376,16 +576,26 @@ struct TALER_EXCHANGEDB_TableData
char *wire_method;
struct GNUNET_TIME_Timestamp start_date;
struct GNUNET_TIME_Timestamp end_date;
- struct TALER_Amount wire_fee;
- struct TALER_Amount closing_fee;
+ struct TALER_WireFeeSet fees;
struct TALER_MasterSignatureP master_sig;
} wire_fee;
struct
{
- uint64_t known_coin_id;
+ 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 TALER_DenominationBlindingKeyP coin_blind;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
struct TALER_Amount amount;
struct GNUNET_TIME_Timestamp timestamp;
uint64_t reserve_out_serial_id;
@@ -394,13 +604,219 @@ struct TALER_EXCHANGEDB_TableData
struct
{
uint64_t known_coin_id;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
struct TALER_CoinSpendSignatureP coin_sig;
- union TALER_DenominationBlindingKeyP coin_blind;
+ 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;
};
@@ -442,7 +858,7 @@ struct TALER_EXCHANGEDB_DenominationKey
/**
* Signed public information about a denomination key.
*/
- struct TALER_EXCHANGEDB_DenominationKeyInformationP issue;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
};
@@ -560,8 +976,15 @@ struct TALER_EXCHANGEDB_Reserve
struct TALER_EXCHANGEDB_DenominationKeyMetaData
{
/**
- * Start time of the validity period for this key.
- */
+ * 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;
/**
@@ -605,29 +1028,10 @@ struct TALER_EXCHANGEDB_DenominationKeyMetaData
struct TALER_Amount value;
/**
- * The fee the exchange charges when a coin of this type is withdrawn.
- * (can be zero).
- */
- struct TALER_Amount fee_withdraw;
-
- /**
- * The fee the exchange charges when a coin of this type is deposited.
- * (can be zero).
+ * The fees the exchange charges for operations with
+ * coins of this denomination.
*/
- struct TALER_Amount fee_deposit;
-
- /**
- * The fee the exchange charges when a coin of this type is refreshed.
- * (can be zero).
- */
- struct TALER_Amount 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_Amount fee_refund;
+ struct TALER_DenomFeeSet fees;
/**
* Age restriction for the denomination. (can be zero). If not zero, the bits
@@ -643,7 +1047,7 @@ struct TALER_EXCHANGEDB_DenominationKeyMetaData
* A value of 0 means that the denomination does not support the extension for
* age-restriction.
*/
- struct TALER_AgeMask age_restrictions;
+ struct TALER_AgeMask age_mask;
};
@@ -663,7 +1067,7 @@ typedef void
(*TALER_EXCHANGEDB_DenominationsCallback)(
void *cls,
const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
const struct TALER_MasterSignatureP *master_sig,
bool recoup_possible);
@@ -687,6 +1091,39 @@ typedef void
/**
+ * 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 *`
@@ -715,7 +1152,7 @@ typedef void
(*TALER_EXCHANGEDB_AuditorDenominationsCallback)(
void *cls,
const struct TALER_AuditorPublicKeyP *auditor_pub,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_AuditorSignatureP *auditor_sig);
@@ -735,7 +1172,7 @@ struct TALER_EXCHANGEDB_CollectableBlindcoin
/**
* Hash of the denomination key (which coin was generated).
*/
- struct TALER_DenominationHash denom_pub_hash;
+ struct TALER_DenominationHashP denom_pub_hash;
/**
* Value of the coin being exchangeed (matching the denomination key)
@@ -768,7 +1205,7 @@ struct TALER_EXCHANGEDB_CollectableBlindcoin
* Hash over the blinded message, needed to verify
* the @e reserve_sig.
*/
- struct TALER_BlindedCoinHash h_coin_envelope;
+ struct TALER_BlindedCoinHashP h_coin_envelope;
/**
* Signature confirming the withdrawal, matching @e reserve_pub,
@@ -779,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.
*/
@@ -794,7 +1304,7 @@ struct TALER_EXCHANGEDB_Recoup
* Blinding factor supplied to prove to the exchange that
* the coin came from this reserve.
*/
- union TALER_DenominationBlindingKeyP coin_blind;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
/**
* Signature of the coin of type
@@ -821,6 +1331,23 @@ struct TALER_EXCHANGEDB_Recoup
/**
+ * 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;
+};
+
+
+/**
* Information the exchange records about a recoup request
* in a coin history.
*/
@@ -831,7 +1358,7 @@ struct TALER_EXCHANGEDB_RecoupListEntry
* Blinding factor supplied to prove to the exchange that
* the coin came from this reserve.
*/
- union TALER_DenominationBlindingKeyP coin_blind;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
/**
* Signature of the coin of type
@@ -842,7 +1369,7 @@ struct TALER_EXCHANGEDB_RecoupListEntry
/**
* Hash of the public denomination key used to sign the coin.
*/
- struct TALER_DenominationHash h_denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
/**
* Public key of the reserve the coin was paid back into.
@@ -879,7 +1406,7 @@ struct TALER_EXCHANGEDB_RecoupRefreshListEntry
* Blinding factor supplied to prove to the exchange that
* the coin came from this @e old_coin_pub.
*/
- union TALER_DenominationBlindingKeyP coin_blind;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
/**
* Signature of the coin of type
@@ -906,6 +1433,173 @@ struct TALER_EXCHANGEDB_RecoupRefreshListEntry
/**
+ * 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;
+
+};
+
+
+/**
* @brief Types of operations on a reserve.
*/
enum TALER_EXCHANGEDB_ReserveOperation
@@ -931,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
};
@@ -982,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
@@ -1022,24 +1880,23 @@ struct TALER_EXCHANGEDB_Deposit
* Hash over the proposal data between merchant and customer
* (remains unknown to the Exchange).
*/
- struct TALER_PrivateContractHash h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
/**
* Salt used by the merchant to compute "h_wire".
*/
- struct TALER_WireSalt wire_salt;
+ struct TALER_WireSaltP wire_salt;
/**
- * Information about the receiver for executing the transaction. URI in
- * payto://-format.
+ * Hash over inputs from the wallet to customize the contract.
*/
- char *receiver_wire_account;
+ struct GNUNET_HashCode wallet_data_hash;
/**
- * Additional details for extensions relevant for this
- * deposit operation, possibly NULL!
+ * Hash over the policy data for this deposit (remains unknown to the
+ * Exchange). Needed for the verification of the deposit's signature
*/
- json_t *extension_details;
+ struct TALER_ExtensionPolicyHashP h_policy;
/**
* Time when this request was generated. Used, for example, to
@@ -1082,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;
+
};
@@ -1110,23 +1983,45 @@ struct TALER_EXCHANGEDB_DepositListEntry
* Hash over the proposa data between merchant and customer
* (remains unknown to the Exchange).
*/
- struct TALER_PrivateContractHash h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Hash over inputs from the wallet to customize the contract.
+ */
+ struct GNUNET_HashCode wallet_data_hash;
/**
* Hash of the public denomination key used to sign the coin.
*/
- struct TALER_DenominationHash h_denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
/**
- * Detailed information about the receiver for executing the transaction.
- * URL in payto://-format.
+ * Age commitment hash, if applicable to the denomination. Should be all
+ * zeroes if age commitment is not applicable to the denonimation.
*/
- char *receiver_wire_account;
+ struct TALER_AgeCommitmentHash h_age_commitment;
/**
* Salt used to compute h_wire from the @e receiver_wire_account.
*/
- struct TALER_WireSalt wire_salt;
+ 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.
+ */
+ struct TALER_Amount amount_with_fee;
+
+ /**
+ * Depositing fee.
+ */
+ struct TALER_Amount deposit_fee;
/**
* Time when this request was generated. Used, for example, to
@@ -1159,15 +2054,25 @@ struct TALER_EXCHANGEDB_DepositListEntry
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?
@@ -1197,7 +2102,7 @@ struct TALER_EXCHANGEDB_RefundListEntry
* Hash over the proposal data between merchant and customer
* (remains unknown to the Exchange).
*/
- struct TALER_PrivateContractHash h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
/**
* Merchant-generated REFUND transaction ID to detect duplicate
@@ -1303,7 +2208,19 @@ struct TALER_EXCHANGEDB_MeltListEntry
/**
* Hash of the public denomination key used to sign the coin.
*/
- struct TALER_DenominationHash h_denom_pub;
+ 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,
@@ -1330,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
@@ -1369,11 +2438,31 @@ struct TALER_EXCHANGEDB_LinkList
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;
};
@@ -1412,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
};
@@ -1482,61 +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;
-/**
- * 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 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
- * @return transaction status code, #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT to continue to iterate
- */
-typedef enum GNUNET_DB_QueryStatus
-(*TALER_EXCHANGEDB_MatchingDepositIterator)(
- void *cls,
- uint64_t rowid,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct TALER_PrivateContractHash *h_contract_terms);
+ /**
+ * 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_target unique ID of the receiver account
- * @param payto_uri how to pay the merchant, URI in payto://-format;
- * @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 TALER_PrivateContractHash *h_contract_terms,
- uint64_t wire_target,
- const char *payto_uri);
+};
/**
@@ -1557,6 +2627,28 @@ 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.
*
@@ -1579,12 +2671,166 @@ typedef enum GNUNET_GenericReturnValue
/**
+ * 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 TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_DenominationPublicKey *denom_pub);
+
+
+/**
* Function called with details about coins that were melted,
* with the goal of auditing the refresh's execution.
*
* @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
@@ -1597,6 +2843,7 @@ typedef enum GNUNET_GenericReturnValue
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,
@@ -1627,7 +2874,7 @@ struct TALER_EXCHANGEDB_RefreshRevealedCoin
/**
* Hash of the public denomination key of the coin.
*/
- struct TALER_DenominationHash h_denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
/**
* Signature of the original coin being refreshed over the
@@ -1638,7 +2885,7 @@ struct TALER_EXCHANGEDB_RefreshRevealedCoin
/**
* Hash of the blinded new coin, that is @e coin_ev.
*/
- struct TALER_BlindedCoinHash coin_envelope_hash;
+ struct TALER_BlindedCoinHashP coin_envelope_hash;
/**
* Signature generated by the exchange over the coin (in blinded format).
@@ -1646,58 +2893,43 @@ struct TALER_EXCHANGEDB_RefreshRevealedCoin
struct TALER_BlindedDenominationSignature coin_sig;
/**
- * Blinded message to be signed (in envelope), with @e coin_env_size bytes.
+ * Values contributed from the exchange to the
+ * coin generation (see /csr).
*/
- void *coin_ev;
+ struct TALER_ExchangeWithdrawValues exchange_vals;
/**
- * Number of bytes in @e coin_ev.
+ * Blinded message to be signed (in envelope).
*/
- size_t coin_ev_size;
+ struct TALER_BlindedPlanchet blinded_planchet;
};
/**
- * Types of operations that require KYC checks.
+ * Information per Clause-Schnorr (CS) fresh coin to
+ * be persisted for idempotency during refreshes-reveal.
*/
-enum TALER_EXCHANGEDB_KycType
+struct TALER_EXCHANGEDB_CsRevealFreshCoinData
{
-
/**
- * It is unclear for which type of KYC operation
- * this information is.
+ * Denomination of the fresh coin.
*/
- TALER_EXCHANGEDB_KYC_UNKNOWN = 0,
+ struct TALER_DenominationHashP new_denom_pub_hash;
/**
- * KYC to be applied for simple withdraws without
- * the involvement of wallet-to-wallet payments.
- * Tied to the payto:// of the debited account.
+ * Blind signature of the fresh coin (possibly updated
+ * in case if a replay!).
*/
- TALER_EXCHANGEDB_KYC_WITHDRAW = 1,
+ struct TALER_BlindedDenominationSignature bsig;
/**
- * KYC to be applied for simple deposits to a
- * merchant's bank account. Tied to the payto://
- * of the credited account.
+ * 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).
*/
- TALER_EXCHANGEDB_KYC_DEPOSIT = 2,
-
- /**
- * KYC that is self-applied by a wallet that is exceeding the amount
- * threshold. Tied to the reserve-account public key that identifies the
- * funds-holding wallet.
- */
- TALER_EXCHANGEDB_KYC_BALANCE = 3,
-
- /**
- * KYC that is triggered upon wallet-to-wallet
- * payments for the recipient of funds. Tied to the
- * reserve public key that identifies the receiving
- * wallet.
- */
- TALER_EXCHANGEDB_KYC_W2W = 4
+ uint32_t coin_off;
};
@@ -1707,15 +2939,10 @@ enum TALER_EXCHANGEDB_KycType
struct TALER_EXCHANGEDB_KycStatus
{
/**
- * Number that identifies the KYC target the operation
+ * Number that identifies the KYC requirement the operation
* was about.
*/
- uint64_t payment_target_uuid;
-
- /**
- * What kind of KYC operation is this?
- */
- enum TALER_EXCHANGEDB_KycType type;
+ uint64_t requirement_row;
/**
* True if the KYC status is "satisfied".
@@ -1725,6 +2952,36 @@ struct TALER_EXCHANGEDB_KycStatus
};
+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.
*
@@ -1751,6 +3008,7 @@ 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
*/
@@ -1762,8 +3020,9 @@ typedef enum GNUNET_GenericReturnValue
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_MerchantSignatureP *merchant_sig,
- const struct TALER_PrivateContractHash *h_contract_terms,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
uint64_t rtransaction_id,
+ bool full_refund,
const struct TALER_Amount *amount_with_fee);
@@ -1795,22 +3054,31 @@ typedef enum GNUNET_GenericReturnValue
*
* @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 struct TALER_MasterSignatureP *master_sig);
+ 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 wire_fee the wire fee we charge
- * @param closing_fee the closing fee we charge
+ * @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
@@ -1819,8 +3087,32 @@ typedef void
typedef void
(*TALER_EXCHANGEDB_WireFeeCallback)(
void *cls,
- const struct TALER_Amount *wire_fee,
- const struct TALER_Amount *closing_fee,
+ 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);
@@ -1843,7 +3135,7 @@ typedef enum GNUNET_GenericReturnValue
(*TALER_EXCHANGEDB_WithdrawCallback)(
void *cls,
uint64_t rowid,
- const struct TALER_BlindedCoinHash *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,
@@ -1873,8 +3165,8 @@ typedef void
* @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_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
@@ -1888,8 +3180,9 @@ typedef void
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_PrivateContractHash *h_contract_terms,
+ 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,
@@ -1919,6 +3212,26 @@ typedef enum GNUNET_GenericReturnValue
/**
+ * 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
@@ -1962,7 +3275,7 @@ typedef enum GNUNET_GenericReturnValue
const struct TALER_CoinPublicInfo *coin,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const union TALER_DenominationBlindingKeyP *coin_blind);
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_blind);
/**
@@ -1988,11 +3301,38 @@ typedef enum GNUNET_GenericReturnValue
struct GNUNET_TIME_Timestamp timestamp,
const struct TALER_Amount *amount,
const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
- const struct TALER_DenominationHash *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 union 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);
/**
@@ -2007,6 +3347,8 @@ typedef enum GNUNET_GenericReturnValue
* @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 enum GNUNET_GenericReturnValue
@@ -2018,7 +3360,8 @@ typedef enum GNUNET_GenericReturnValue
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);
/**
@@ -2041,15 +3384,20 @@ typedef void
* @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_Timestamp expiration_date);
+ struct GNUNET_TIME_Timestamp expiration_date,
+ uint64_t close_request_row);
/**
@@ -2070,34 +3418,72 @@ typedef void
uint64_t rowid,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const union TALER_DenominationBlindingKeyP *coin_blind,
- const struct TALER_BlindedCoinHash *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 payto_uri where should the funds be wired; URI 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 char *payto_uri,
- struct GNUNET_TIME_Timestamp deadline,
- bool tiny,
- bool 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);
/**
@@ -2113,7 +3499,47 @@ typedef void
(*TALER_EXCHANGEDB_DenominationCallback)(
void *cls,
const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue);
+ 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);
/**
@@ -2144,15 +3570,20 @@ struct TALER_EXCHANGEDB_Plugin
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
*/
enum GNUNET_GenericReturnValue
- (*create_tables)(void *cls);
+ (*create_tables)(void *cls,
+ bool support_partitions,
+ uint32_t num_partitions);
/**
@@ -2180,6 +3611,18 @@ struct TALER_EXCHANGEDB_Plugin
(*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.
@@ -2272,7 +3715,7 @@ struct TALER_EXCHANGEDB_Plugin
(*insert_denomination_info)(
void *cls,
const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue);
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
/**
@@ -2286,8 +3729,8 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*get_denomination_info)(
void *cls,
- const struct TALER_DenominationHash *denom_pub_hash,
- struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue);
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
/**
@@ -2377,144 +3820,240 @@ struct TALER_EXCHANGEDB_Plugin
* @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.
- * @param[out] kyc set to the KYC status of the reserve
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
(*reserves_get)(void *cls,
- struct TALER_EXCHANGEDB_Reserve *reserve,
- struct TALER_EXCHANGEDB_KycStatus *kyc);
+ struct TALER_EXCHANGEDB_Reserve *reserve);
/**
- * Set the KYC status to "OK" for a bank account.
+ * Get the origin of funds of a reserve.
*
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param payment_target_uuid which account has been checked
- * @param id ID data to persist
+ * @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
- (*set_kyc_ok)(void *cls,
- uint64_t payment_target_uuid,
- const char *id);
+ (*reserves_get_origin)(
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_PaytoHashP *h_payto);
/**
- * Get the @a kyc status and @a h_payto by UUID.
+ * Extract next KYC alert. Deletes the alert.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param payment_target_uuid which account to get the KYC status for
- * @param[out] h_payto set to the hash of the account's payto URI (unsalted)
- * @param[out] kyc set to the KYC status of the account
+ * @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
- (*select_kyc_status)(void *cls,
- uint64_t payment_target_uuid,
- struct TALER_PaytoHash *h_payto,
- struct TALER_EXCHANGEDB_KycStatus *kyc);
+ (*drain_kyc_alert)(void *cls,
+ uint32_t trigger_type,
+ struct TALER_PaytoHashP *h_payto);
/**
- * Get the KYC status for a wallet. If the status is unknown,
- * inserts a new status record (hence INsertSELECT).
+ * 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 reserve_pub public key of the wallet
- * @param[out] kyc set to the KYC status of the wallet
- * @return transaction status
+ * @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
- (*inselect_wallet_kyc_status)(
+ (*reserves_in_insert)(
void *cls,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- struct TALER_EXCHANGEDB_KycStatus *kyc);
+ const struct TALER_EXCHANGEDB_ReserveInInfo *reserves,
+ unsigned int reserves_length,
+ enum GNUNET_DB_QueryStatus *results);
/**
- * Insert a incoming transaction into reserves. New reserves are
- * also created through this function.
+ * Locate a nonce for use with a particular public key.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @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 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
- (*reserves_in_insert)(void *cls,
- 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);
+ (*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 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,
- const struct TALER_BlindedCoinHash *h_blind,
- struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable);
+ const struct TALER_BlindedCoinHashP *bch,
+ struct TALER_EXCHANGEDB_CollectableBlindcoin *
+ collectable);
/**
- * Perform withdraw operation, checking for sufficient balance
- * and possibly persisting the withdrawal details.
+ * 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 `struct PostgresClosure` with the plugin-specific state
- * @param collectable corresponding collectable coin (blind signature)
- * if a coin is found
* @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] kyc set to the KYC status of the reserve
+ * @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_withdraw)(
+ (*do_batch_withdraw)(
void *cls,
- const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
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_EXCHANGEDB_KycStatus *kyc_ok,
+ struct TALER_Amount *reserve_balance,
+ bool *age_ok,
+ uint16_t *allowed_maximum_age,
uint64_t *ruuid);
/**
- * Check that reserve remains below threshold for KYC
- * checks after withdraw operation.
+ * 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 ruuid identifies the reserve to check
- * @param withdraw_start starting point to accumulate from
- * @param upper_limit maximum amount allowed
- * @param[out] below_limit set to true if the limit was not exceeded
+ * @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
- (*do_withdraw_limit_check)(
+ (*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,
- struct GNUNET_TIME_Absolute withdraw_start,
- const struct TALER_Amount *upper_limit,
- bool *below_limit);
+ 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
+ (*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);
/**
@@ -2522,31 +4061,30 @@ struct TALER_EXCHANGEDB_Plugin
* of the coin and possibly persisting the deposit details.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param deposit deposit operation details
- * @param known_coin_id row of the coin in the known_coins table
- * @param h_payto hash of the merchant's payto URI
+ * @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] in_conflict set to true if the deposit conflicted
+ * @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_Deposit *deposit,
- uint64_t known_coin_id,
- const struct TALER_PaytoHash *h_payto,
- bool extension_blocked,
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd,
struct GNUNET_TIME_Timestamp *exchange_timestamp,
bool *balance_ok,
- bool *in_conflict);
+ 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 `struct PostgresClosure` with the plugin-specific state
+ * @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
@@ -2557,6 +4095,7 @@ struct TALER_EXCHANGEDB_Plugin
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,
@@ -2564,6 +4103,41 @@ struct TALER_EXCHANGEDB_Plugin
/**
+ * 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.
*
@@ -2610,7 +4184,7 @@ struct TALER_EXCHANGEDB_Plugin
void *cls,
const struct TALER_ReservePublicKeyP *reserve_pub,
uint64_t reserve_out_serial_id,
- const union TALER_DenominationBlindingKeyP *coin_bks,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
uint64_t known_coin_id,
const struct TALER_CoinSpendSignatureP *coin_sig,
@@ -2640,7 +4214,7 @@ struct TALER_EXCHANGEDB_Plugin
void *cls,
const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
uint64_t rrc_serial,
- const union TALER_DenominationBlindingKeyP *coin_bks,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
uint64_t known_coin_id,
const struct TALER_CoinSpendSignatureP *coin_sig,
@@ -2650,11 +4224,18 @@ struct TALER_EXCHANGEDB_Plugin
/**
- * Get all of the transaction history associated with the specified
- * reserve.
+ * 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 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
@@ -2662,11 +4243,28 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*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);
/**
+ * 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
@@ -2686,7 +4284,7 @@ struct TALER_EXCHANGEDB_Plugin
*/
long long
(*count_known_coins) (void *cls,
- const struct TALER_DenominationHash *denom_pub_hash);
+ const struct TALER_DenominationHashP *denom_pub_hash);
/**
@@ -2727,22 +4325,53 @@ struct TALER_EXCHANGEDB_Plugin
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 = -4,
+ TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS = -6,
+
}
(*ensure_coin_known)(void *cls,
const struct TALER_CoinPublicInfo *coin,
uint64_t *known_coin_id,
- struct TALER_DenominationHash *denom_pub_hash,
- struct TALER_AgeHash *age_hash);
+ 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 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
@@ -2750,6 +4379,21 @@ struct TALER_EXCHANGEDB_Plugin
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.
@@ -2764,7 +4408,26 @@ struct TALER_EXCHANGEDB_Plugin
(*get_coin_denomination)(void *cls,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
uint64_t *known_coin_id,
- struct TALER_DenominationHash *denom_hash);
+ struct TALER_DenominationHashP *denom_hash);
+
+
+ /**
+ * 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 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
+ (*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);
/**
@@ -2786,8 +4449,8 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*have_deposit2)(
void *cls,
- const struct TALER_PrivateContractHash *h_contract_terms,
- const struct TALER_MerchantWireHash *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,
struct GNUNET_TIME_Timestamp refund_deadline,
@@ -2796,21 +4459,6 @@ struct TALER_EXCHANGEDB_Plugin
/**
- * Insert information about deposited coin into the database.
- * Used in tests and for benchmarking.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param exchange_timestamp time the exchange received the deposit request
- * @param deposit deposit information to store
- * @return query result status
- */
- enum GNUNET_DB_QueryStatus
- (*insert_deposit)(void *cls,
- struct GNUNET_TIME_Timestamp exchange_timestamp,
- const struct TALER_EXCHANGEDB_Deposit *deposit);
-
-
- /**
* Insert information about refunded coin into the database.
* Used in tests and for benchmarking.
*
@@ -2835,99 +4483,151 @@ struct TALER_EXCHANGEDB_Plugin
* @return query result status
*/
enum GNUNET_DB_QueryStatus
- (*select_refunds_by_coin)(void *cls,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_PrivateContractHash *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 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,
- 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);
/**
- * 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().
+ * 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 deposit_rowid identifies the deposit row to modify
- * @return query result status
+ * @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
- (*mark_deposit_done)(void *cls,
- uint64_t rowid);
+ (*aggregate)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ 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, the refund deadlines must both be in the past and
- * the KYC status must be 'ok'.
+ * Create a new entry in the transient aggregation table.
*
* @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 kyc_off true if we should not check the KYC status because
- * this exchange does not need/support KYC checks.
- * @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 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
- (*get_ready_deposit)(void *cls,
- uint64_t start_shard_row,
- uint64_t end_shard_row,
- bool kyc_off,
- TALER_EXCHANGEDB_DepositIterator deposit_cb,
- void *deposit_cb_cls);
-
+ (*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);
-/**
- * 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
/**
- * Obtain information about other pending deposits for the same
- * destination. Those deposits must not already be "done".
+ * Select existing entry in the transient aggregation table.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @param h_wire destination of the wire transfer
+ * @param h_payto 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 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
- (*iterate_matching_deposits)(
+ (*select_aggregation_transient)(
void *cls,
- uint64_t wire_target,
+ const struct TALER_PaytoHashP *h_payto,
const struct TALER_MerchantPublicKeyP *merchant_pub,
- TALER_EXCHANGEDB_MatchingDepositIterator deposit_cb,
- void *deposit_cb_cls,
- uint32_t limit);
+ const char *exchange_account_section,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_Amount *total);
+
+
+ /**
+ * 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);
+
+
+ /**
+ * 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
+ (*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);
+
+
+ /**
+ * 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
+ (*delete_aggregation_transient)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid);
/**
@@ -3010,20 +4710,35 @@ struct TALER_EXCHANGEDB_Plugin
/**
- * 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 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,
- 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);
/**
@@ -3072,13 +4787,14 @@ struct TALER_EXCHANGEDB_Plugin
* @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,
- const struct TALER_PrivateContractHash *h_contract_terms,
- const struct TALER_MerchantWireHash *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,
bool *pending,
@@ -3086,22 +4802,8 @@ struct TALER_EXCHANGEDB_Plugin
struct GNUNET_TIME_Timestamp *exec_time,
struct TALER_Amount *amount_with_fee,
struct TALER_Amount *deposit_fee,
- struct TALER_EXCHANGEDB_KycStatus *kyc);
-
-
- /**
- * Function called to insert aggregation information into the DB.
- *
- * @param cls closure
- * @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
- */
- enum GNUNET_DB_QueryStatus
- (*insert_aggregation_tracking)(
- void *cls,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- unsigned long long deposit_serial_id);
+ struct TALER_EXCHANGEDB_KycStatus *kyc,
+ enum TALER_AmlDecisionState *aml_decision);
/**
@@ -3111,8 +4813,7 @@ struct TALER_EXCHANGEDB_Plugin
* @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 fees how high is are the wire fees
* @param master_sig signature over the above by the exchange master key
* @return transaction status code
*/
@@ -3121,12 +4822,36 @@ struct TALER_EXCHANGEDB_Plugin
const char *wire_method,
struct GNUNET_TIME_Timestamp start_date,
struct GNUNET_TIME_Timestamp end_date,
- const struct TALER_Amount *wire_fee,
- const struct TALER_Amount *closing_fee,
+ const struct TALER_WireFeeSet *fees,
const struct TALER_MasterSignatureP *master_sig);
/**
+ * Insert global fee set 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
+ (*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
@@ -3134,8 +4859,7 @@ struct TALER_EXCHANGEDB_Plugin
* @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
*/
@@ -3145,12 +4869,37 @@ struct TALER_EXCHANGEDB_Plugin
struct GNUNET_TIME_Timestamp date,
struct GNUNET_TIME_Timestamp *start_date,
struct GNUNET_TIME_Timestamp *end_date,
- struct TALER_Amount *wire_fee,
- struct TALER_Amount *closing_fee,
+ 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.
*
@@ -3168,6 +4917,152 @@ struct TALER_EXCHANGEDB_Plugin
/**
+ * 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
@@ -3177,6 +5072,7 @@ struct TALER_EXCHANGEDB_Plugin
* @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
@@ -3184,9 +5080,11 @@ struct TALER_EXCHANGEDB_Plugin
const struct TALER_ReservePublicKeyP *reserve_pub,
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);
/**
@@ -3265,7 +5163,7 @@ struct TALER_EXCHANGEDB_Plugin
*
* @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 wire_account details about the receiver account of the wire transfer,
* including 'url' in payto://-format
* @param amount amount that was transmitted
@@ -3278,7 +5176,7 @@ struct TALER_EXCHANGEDB_Plugin
void *cls,
struct GNUNET_TIME_Timestamp date,
const struct TALER_WireTransferIdentifierRawP *wtid,
- uint64_t wire_target,
+ const struct TALER_PaytoHashP *h_payto,
const char *exchange_account_section,
const struct TALER_Amount *amount);
@@ -3306,10 +5204,138 @@ struct TALER_EXCHANGEDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*select_deposits_above_serial_id)(void *cls,
- 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
@@ -3414,7 +5440,8 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*select_wire_out_above_serial_id)(void *cls,
uint64_t serial_id,
- TALER_EXCHANGEDB_WireTransferOutCallback cb,
+ TALER_EXCHANGEDB_WireTransferOutCallback
+ cb,
void *cb_cls);
/**
@@ -3473,8 +5500,8 @@ struct TALER_EXCHANGEDB_Plugin
/**
- * 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 serial_id lowest serial ID to include (select larger or equal)
@@ -3483,6 +5510,24 @@ struct TALER_EXCHANGEDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
+ (*select_reserve_open_above_serial_id)(
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveOpenCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * 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
(*select_reserve_closed_above_serial_id)(
void *cls,
uint64_t serial_id,
@@ -3495,16 +5540,17 @@ struct TALER_EXCHANGEDB_Plugin
* from given the hash of the blinded coin.
*
* @param cls closure
- * @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,
- const struct TALER_BlindedCoinHash *h_blind_ev,
- struct TALER_ReservePublicKeyP *reserve_pub,
- uint64_t *reserve_out_serial_id);
+ (*get_reserve_by_h_blind)(
+ void *cls,
+ const struct TALER_BlindedCoinHashP *bch,
+ struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *reserve_out_serial_id);
/**
@@ -3518,10 +5564,11 @@ struct TALER_EXCHANGEDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*get_old_coin_by_h_blind)(void *cls,
- const struct TALER_BlindedCoinHash *h_blind_ev,
- struct TALER_CoinSpendPublicKeyP *old_coin_pub,
- uint64_t *rrc_serial);
+ (*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);
/**
@@ -3536,7 +5583,7 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*insert_denomination_revocation)(
void *cls,
- const struct TALER_DenominationHash *denom_pub_hash,
+ const struct TALER_DenominationHashP *denom_pub_hash,
const struct TALER_MasterSignatureP *master_sig);
@@ -3551,31 +5598,71 @@ struct TALER_EXCHANGEDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*get_denomination_revocation)(void *cls,
- const struct
- TALER_DenominationHash *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 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 GNUNET_TIME_Timestamp start_date,
- struct GNUNET_TIME_Timestamp 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);
/**
@@ -3587,9 +5674,10 @@ struct TALER_EXCHANGEDB_Plugin
* @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_auditor_timestamp)(
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct GNUNET_TIME_Timestamp *last_date);
/**
@@ -3603,10 +5691,11 @@ struct TALER_EXCHANGEDB_Plugin
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*lookup_auditor_status)(void *cls,
- const struct TALER_AuditorPublicKeyP *auditor_pub,
- char **auditor_url,
- bool *enabled);
+ (*lookup_auditor_status)(
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ char **auditor_url,
+ bool *enabled);
/**
@@ -3621,11 +5710,12 @@ struct TALER_EXCHANGEDB_Plugin
* @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);
+ (*insert_auditor)(
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name,
+ struct GNUNET_TIME_Timestamp start_date);
/**
@@ -3641,12 +5731,13 @@ struct TALER_EXCHANGEDB_Plugin
* @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);
+ (*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);
/**
@@ -3668,17 +5759,27 @@ struct TALER_EXCHANGEDB_Plugin
*
* @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 struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority);
/**
@@ -3686,15 +5787,27 @@ struct TALER_EXCHANGEDB_Plugin
*
* @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);
@@ -3730,6 +5843,20 @@ struct TALER_EXCHANGEDB_Plugin
/**
+ * 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
@@ -3770,7 +5897,7 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*lookup_denomination_key)(
void *cls,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta);
@@ -3787,7 +5914,7 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*add_denomination_key)(
void *cls,
- const struct TALER_DenominationHash *h_denom_pub,
+ 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);
@@ -3838,7 +5965,7 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*insert_auditor_denom_sig)(
void *cls,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_AuditorPublicKeyP *auditor_pub,
const struct TALER_AuditorSignatureP *auditor_sig);
@@ -3855,7 +5982,7 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*select_auditor_denom_sig)(
void *cls,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_AuditorPublicKeyP *auditor_pub,
struct TALER_AuditorSignatureP *auditor_sig);
@@ -3867,10 +5994,7 @@ struct TALER_EXCHANGEDB_Plugin
* @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] wire_fee wire fee for that time period; if
- * different wire fee exists within this time
- * period, an 'invalid' amount is returned.
- * @param[out] closing_fee wire fee for that time period; if
+ * @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
@@ -3881,8 +6005,32 @@ struct TALER_EXCHANGEDB_Plugin
const char *wire_method,
struct GNUNET_TIME_Timestamp start_time,
struct GNUNET_TIME_Timestamp end_time,
- struct TALER_Amount *wire_fee,
- struct TALER_Amount *closing_fee);
+ 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);
/**
@@ -3924,6 +6072,7 @@ struct TALER_EXCHANGEDB_Plugin
* 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
@@ -3954,6 +6103,20 @@ struct TALER_EXCHANGEDB_Plugin
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.
@@ -4015,41 +6178,1066 @@ struct TALER_EXCHANGEDB_Plugin
* changed.
*
* @param cls the @e cls of this struct with the plugin-specific state
- * @return transaction status code
+ * @return #GNUNET_OK on success
+ * #GNUNET_SYSERR on failure
*/
- enum GNUNET_DB_QueryStatus
+ enum GNUNET_GenericReturnValue
(*delete_shard_locks)(void *cls);
+
/**
- * Function called to save the configuration of an extension
- * (age-restriction, peer2peer, ...)
+ * 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 config JSON object of the configuration as string
- * @param config_sig signature of the configuration by the offline master key
+ * @param manifest JSON object of the Manifest as string, maybe NULL (== disabled extension)
* @return transaction status code
*/
enum GNUNET_DB_QueryStatus
- (*set_extension_config)(void *cls,
- const char *extension_name,
- const char *config,
- const struct TALER_MasterSignatureP *config_sig);
+ (*set_extension_manifest)(void *cls,
+ const char *extension_name,
+ const char *manifest);
+
/**
- * Function called to retrieve the configuration of an extension
- * (age-restriction, peer2peer, ...)
+ * 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] config JSON object of the configuration as string
- * @param[out] config_sig signature of the configuration by the master key
+ * @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
- (*get_extension_config)(void *cls,
- const char *extension_name,
- char **config);
+ (*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
index 243811eb5..1eb567f72 100644
--- a/src/include/taler_extensions.h
+++ b/src/include/taler_extensions.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 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 General Public License as published by the Free Software
@@ -22,42 +22,199 @@
#define TALER_EXTENSIONS_H
#include <gnunet/gnunet_util_lib.h>
-#include "taler_crypto_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_ReturnValue
-{
- TALER_Extension_OK = 0,
- TALER_Extension_ERROR_PARSING = 1,
- TALER_Extension_ERROR_INVALID = 2,
- TALER_Extension_ERROR_SYS = 3
-};
-
enum TALER_Extension_Type
{
- TALER_Extension_AgeRestriction = 0,
- TALER_Extension_Peer2Peer = 1,
- TALER_Extension_Max = 2 // Must be last
+ 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;
+
/*
- * TODO oec: documentation
+ * @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;
- enum GNUNET_GenericReturnValue (*test_config)(const json_t *config);
- enum GNUNET_GenericReturnValue (*parse_and_set_config)(struct
- TALER_Extension *this,
- const json_t *config);
- json_t *(*config_to_json)(const struct TALER_Extension *this);
+
+ /**
+ * @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;
};
/**
@@ -65,20 +222,121 @@ struct TALER_Extension
*/
/**
- * Finds and returns a supported extension by a given name.
+ * @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
- * @param extensions list of TALER_Extensions as haystack, terminated by an entry of type TALER_Extension_Max
- * @param[out] ext set to the extension, if found, NULL otherwise
- * @return GNUNET_OK if extension was found, GNUNET_NO otherwise
+ * @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_extension_get_by_name (const char *name,
- const struct TALER_Extension **extensions,
- const struct TALER_Extension **ext);
+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 \
@@ -88,59 +346,41 @@ TALER_extension_get_by_name (const char *name,
* 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_DEFAULT_AGE_MASK (1 | 1 << 8 | 1 << 10 | 1 << 12 | 1 \
- << 14 | 1 << 16 | 1 << 18 | 1 \
- << 21)
+#define TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_GROUPS "8:10:12:14:16:18:21"
-/**
- * @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" parses into the following list of age
- * groups
- * 0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-20, 21-...
- * which then is represented as 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 TALER_Extension_ReturnValue
-TALER_parse_age_group_string (const char *groups,
- struct TALER_AgeMask *mask);
-/**
- * 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.
+/*
+ * @brief Configuration for Age Restriction
*/
-char *
-TALER_age_mask_to_string (const struct TALER_AgeMask *mask);
+struct TALER_AgeRestrictionConfig
+{
+ struct TALER_AgeMask mask;
+ uint8_t num_groups;
+};
/**
- * @brief Reads the age groups from the configuration and sets the
- * corresponding age mask.
+ * @brief Retrieve the age restriction configuration
*
- * @param cfg
- * @param[out] mask for age restriction, will be set to 0 if age restriction is disabled.
- * @return Error if extension for age restriction was set but age groups were
- * invalid, OK otherwise.
+ * @return age restriction configuration if present, otherwise NULL.
*/
-enum TALER_Extension_ReturnValue
-TALER_get_age_mask (const struct GNUNET_CONFIGURATION_Handle *cfg,
- struct TALER_AgeMask *mask);
+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);
-/*
- * TALER Peer2Peer Extension
- * TODO oec
+/**
+ * @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 16135a4d7..6b34f4730 100644
--- a/src/include/taler_fakebank_lib.h
+++ b/src/include/taler_fakebank_lib.h
@@ -76,6 +76,35 @@ TALER_FAKEBANK_start2 (uint16_t port,
/**
+ * 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 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
+ */
+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);
+
+
+/**
* 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()).
diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h
index 102b3a6ff..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, 2021 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
*/
@@ -61,18 +95,6 @@ TALER_JSON_pack_time_abs_human (const char *name,
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
- * absolute time creating a human-readable timestamp.
- *
- * @param name name of the field to add to the object
- * @param at absolute time to pack
- * @return json pack specification
- */
-struct GNUNET_JSON_PackSpec
-TALER_JSON_pack_time_abs_nbo_human (const char *name,
- struct GNUNET_TIME_AbsoluteNBO at);
-
/**
* Generate packer instruction for a JSON field of type
@@ -119,6 +141,34 @@ TALER_JSON_pack_blinded_denom_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
@@ -132,25 +182,28 @@ TALER_JSON_pack_amount (const char *name,
/**
* Generate packer instruction for a JSON field of type
- * amount.
+ * encrypted contract.
*
* @param name name of the field to add to the object
- * @param amount valid amount to pack
+ * @param econtract the encrypted contract
* @return json pack specification
*/
struct GNUNET_JSON_PackSpec
-TALER_JSON_pack_amount_nbo (const char *name,
- const struct TALER_AmountNBO *amount);
-
+TALER_JSON_pack_econtract (
+ const char *name,
+ const struct TALER_EncryptedContract *econtract);
/**
- * Convert a TALER amount to a JSON object.
+ * Generate packer instruction for a JSON field of type age_commitment
*
- * @param amount the amount
- * @return a json object describing the amount
+ * @param name name of the field to add to the object
+ * @param age_commitment age commitment to add
+ * @return json pack specification
*/
-json_t *
-TALER_JSON_from_amount (const struct TALER_Amount *amount);
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_age_commitment (
+ const char *name,
+ const struct TALER_AgeCommitment *age_commitment);
/**
@@ -160,7 +213,7 @@ 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);
/**
@@ -180,20 +233,19 @@ TALER_JSON_spec_amount (const char *name,
/**
- * Provide specification to parse given JSON object to an amount
- * in network byte order.
- * The @a currency must be a valid pointer while the
- * parsing is done, a copy is not made.
+ * Provide specification to parse given JSON object to
+ * a currency specification.
*
* @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
+ * @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_amount_nbo (const char *name,
- const char *currency,
- struct TALER_AmountNBO *r_amount);
+TALER_JSON_spec_currency_specification (
+ const char *name,
+ const char *currency_code,
+ struct TALER_CurrencySpecification *r_cspec);
/**
@@ -210,19 +262,122 @@ TALER_JSON_spec_amount_any (const char *name,
/**
- * Provide specification to parse given JSON object to an amount
- * in any currency in network byte order.
+ * Provide specification to parse given JSON object to an encrypted contract.
*
* @param name name of the amount field in the JSON
- * @param[out] r_amount where the amount has to be written
+ * @param[out] econtract where to store the encrypted contract
* @return spec for parsing an amount
*/
struct GNUNET_JSON_Specification
-TALER_JSON_spec_amount_any_nbo (const char *name,
- struct TALER_AmountNBO *r_amount);
+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_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.
*
* @param field name of the field
@@ -235,10 +390,116 @@ TALER_JSON_spec_denom_pub (const char *field,
/**
+ * 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
@@ -251,7 +512,7 @@ TALER_JSON_spec_denom_sig (const char *field,
* blinded denomination signature.
*
* @param field name of the field
- * @param sig the blinded signature to initialize
+ * @param[out] sig the blinded signature to initialize
* @return corresponding field spec
*/
struct GNUNET_JSON_Specification
@@ -261,6 +522,33 @@ TALER_JSON_spec_blinded_denom_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_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
@@ -316,20 +604,22 @@ TALER_JSON_spec_i18n_str (const char *name,
*/
enum GNUNET_GenericReturnValue
TALER_JSON_contract_hash (const json_t *json,
- struct TALER_PrivateContractHash *hc);
+ struct TALER_PrivateContractHashP *hc);
/**
- * Take a given contract with "forgettable" fields marked
- * but with 'True' instead of a real salt. Replaces all
- * 'True' values with proper random salts. Fails if any
- * forgettable markers are neither 'True' nor valid salts.
+ * 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[in,out] json JSON to transform
+ * @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 (json_t *json);
+TALER_JSON_contract_seed_forgettable (const json_t *spec,
+ json_t *contract);
/**
@@ -438,34 +728,7 @@ TALER_JSON_get_error_code2 (const void *data,
*/
enum GNUNET_GenericReturnValue
TALER_JSON_merchant_wire_signature_hash (const json_t *wire_s,
- struct TALER_MerchantWireHash *hc);
-
-
-/**
- * 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
- */
-enum GNUNET_GenericReturnValue
-TALER_JSON_exchange_wire_signature_check (
- const json_t *wire_s,
- const struct TALER_MasterPublicKeyP *master_pub);
-
-
-/**
- * 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_MerchantWireHashP *hc);
/**
@@ -532,36 +795,31 @@ TALER_JSON_wire_to_payto (const json_t *wire_s);
/**
- * Hash @a extensions in deposits.
+ * Hash @a policy extensions in deposits.
*
- * @param extensions contract extensions to hash
- * @param[out] ech where to write the extension hash
+ * @param policy contract policy extension to hash
+ * @param[out] ech where to write the policy hash
*/
void
-TALER_deposit_extension_hash (const json_t *extensions,
- struct TALER_ExtensionContractHash *ech);
+TALER_deposit_policy_hash (const json_t *policy,
+ struct TALER_ExtensionPolicyHashP *ech);
/**
- * Hash the @a config of an extension, given as JSON
+ * Hash the @a manifests of extensions, given as JSON
*
- * @param config configuration of the extension
- * @param[out] eh where to write the extension hash
+ * @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_extension_config_hash (const json_t *config,
- struct TALER_ExtensionConfigHash *eh);
+TALER_JSON_extensions_manifests_hash (const json_t *manifests,
+ struct TALER_ExtensionManifestsHashP *eh);
/**
- * Parses a JSON object { "extension": "age_restriction", "mask": <uint32> }.
- *
- * @param root is the json object
- * @param[out] mask on succes, will contain the age mask
- * @return #GNUNET_OK on success and #GNUNET_SYSERR on failure.
+ * Canonicalize a JSON input to a string according to RFC 8785.
*/
-enum GNUNET_GenericReturnValue
-TALER_agemask_parse_json (const json_t *root,
- struct TALER_AgeMask *mask);
+char *
+TALER_JSON_canonicalize (const json_t *input);
#endif /* 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 b0012a645..57d041758 100644
--- a/src/include/taler_mhd_lib.h
+++ b/src/include/taler_mhd_lib.h
@@ -27,6 +27,7 @@
#include <jansson.h>
#include <microhttpd.h>
#include "taler_error_codes.h"
+#include "taler_util.h"
#include <gnunet/gnunet_mhd_compat.h>
@@ -172,8 +173,8 @@ TALER_MHD_reply_json_pack (struct MHD_Connection *connection,
* @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)
+ TALER_MHD_reply_json_steal (connection, GNUNET_JSON_PACK (__VA_ARGS__), \
+ response_code)
/**
@@ -208,6 +209,18 @@ TALER_MHD_reply_with_ec (struct MHD_Connection *connection,
/**
+ * 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]);
+
+
+/**
* Make JSON response object.
*
* @param json the json object
@@ -246,7 +259,7 @@ TALER_MHD_make_json_pack (const char *fmt,
* @return MHD response object
*/
#define TALER_MHD_MAKE_JSON_PACK(...) \
- TALER_MHD_make_json_steal (GNUNET_JSON_PACK (__VA_ARGS__))
+ TALER_MHD_make_json_steal (GNUNET_JSON_PACK (__VA_ARGS__))
/**
@@ -257,8 +270,8 @@ TALER_MHD_make_json_pack (const char *fmt,
* @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))
+ GNUNET_JSON_pack_uint64 ("code", ec), \
+ GNUNET_JSON_pack_string ("hint", TALER_ErrorCode_get_hint (ec))
/**
* Create a response indicating an internal error.
@@ -425,7 +438,201 @@ TALER_MHD_parse_json_array (struct MHD_Connection *connection,
/**
- * Extract 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.
@@ -434,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)
*/
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)
/**
@@ -547,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
diff --git a/src/include/taler_pq_lib.h b/src/include/taler_pq_lib.h
index 2189a4f61..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,50 @@
* @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 x pointer to the query parameter to pass
+ * @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_nbo (const struct TALER_AmountNBO *x);
+TALER_PQ_query_param_amount (
+ const struct GNUNET_PQ_Context *db,
+ const struct TALER_Amount *amount);
/**
- * Generate query parameter 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.
+ * 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 x pointer to the query parameter to pass
+ * @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 TALER_Amount *x);
+TALER_PQ_query_param_amount_with_currency (
+ const struct GNUNET_PQ_Context *db,
+ const struct TALER_Amount *amount);
/**
@@ -58,7 +71,7 @@ TALER_PQ_query_param_amount (const struct TALER_Amount *x);
* public key will be serialized into on variable-size
* BLOB.
*
- * @param x pointer to the query parameter to pass
+ * @param denom_pub pointer to the query parameter to pass
*/
struct GNUNET_PQ_QueryParam
TALER_PQ_query_param_denom_pub (
@@ -70,7 +83,7 @@ TALER_PQ_query_param_denom_pub (
* 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_denom_sig (
@@ -78,11 +91,24 @@ TALER_PQ_query_param_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_blinded_denom_sig (
@@ -90,6 +116,18 @@ TALER_PQ_query_param_blinded_denom_sig (
/**
+ * 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 alg_values pointer to the query parameter to pass
+ */
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_exchange_withdraw_values (
+ const struct TALER_ExchangeWithdrawValues *alg_values);
+
+
+/**
* 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
@@ -102,21 +140,115 @@ TALER_PQ_query_param_json (const json_t *x);
/**
- * Currency amount expected.
+ * 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 public_key pointer to the query parameter to pass
+ */
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_blind_sign_pub (
+ const struct GNUNET_CRYPTO_BlindSignPublicKey *public_key);
+
+
+/**
+ * Generate query parameter for a blind sign private key of variable size.
+ *
+ * @param private_key pointer to the query parameter to pass
+ */
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_blind_sign_priv (
+ const struct GNUNET_CRYPTO_BlindSignPrivateKey *private_key);
+
+
+/**
+ * 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
@@ -167,6 +299,32 @@ TALER_PQ_result_spec_blinded_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
@@ -178,6 +336,92 @@ TALER_PQ_result_spec_json (const char *name,
json_t **jp);
+/**
+ * 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] 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_array_blinded_coin_hash (
+ struct GNUNET_PQ_Context *db,
+ const char *name,
+ size_t *num,
+ struct TALER_BlindedCoinHashP **h_coin_evs);
+
+
+/**
+ * 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 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_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_ */
/* end of include/taler_pq_lib.h */
diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h
deleted file mode 100644
index 3ad1121ca..000000000
--- a/src/include/taler_signatures.h
+++ /dev/null
@@ -1,1731 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file 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) */
-/*********************************************/
-
-/**
- * The given revocation key was revoked and must no longer be used.
- */
-#define TALER_SIGNATURE_MASTER_SIGNING_KEY_REVOKED 1020
-
-/**
- * Add payto URI to the list of our wire methods.
- */
-#define TALER_SIGNATURE_MASTER_ADD_WIRE 1021
-
-/**
- * Remove payto URI from the list of our wire methods.
- */
-#define TALER_SIGNATURE_MASTER_DEL_WIRE 1023
-
-/**
- * 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
-
-/**
- * Add an auditor to the list of our auditors.
- */
-#define TALER_SIGNATURE_MASTER_ADD_AUDITOR 1026
-
-/**
- * Remove an auditor from the list of our auditors.
- */
-#define TALER_SIGNATURE_MASTER_DEL_AUDITOR 1027
-
-/**
- * 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
-
-/**
- * Set the configuration of an extension (age-restriction or peer2peer)
- */
-#define TALER_SIGNATURE_MASTER_EXTENSION 1031
-
-/*********************************************/
-/* 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
-
-/**
- * Signature where the Exchange confirms that it does not know a denomination (hash).
- */
-#define TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN 1042
-
-
-/**
- * Signature where the Exchange confirms that it does not consider a denomination valid for the given operation
- * at this time.
- */
-#define TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED 1043
-
-/**
- * Signature by which an exchange affirms that an account
- * successfully passed the KYC checks.
- */
-#define TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS 1044
-
-
-/**********************/
-/* 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 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
-
-/**
- * Signature using a reserve key by which a wallet
- * requests a payment target UUID for itself.
- * Signs over just a purpose (no body), as the
- * signature only serves to demonstrate that the request
- * comes from the wallet controlling the private key,
- * and not some third party.
- */
-#define TALER_SIGNATURE_WALLET_ACCOUNT_SETUP 1205
-
-/**
- * Signature using a coin key requesting recoup-refresh.
- */
-#define TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH 1206
-
-
-/******************************/
-/* Security module signatures */
-/******************************/
-
-/**
- * Signature on a denomination key announcement.
- */
-#define TALER_SIGNATURE_SM_RSA_DENOMINATION_KEY 1250
-
-/**
- * Signature on an exchange message signing key announcement.
- */
-#define TALER_SIGNATURE_SM_SIGNING_KEY 1251
-
-/*******************/
-/* 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
-
-
-/*******************/
-/* Sync signatures */
-/*******************/
-
-
-/**
- * EdDSA signature for a backup upload.
- */
-#define TALER_SIGNATURE_SYNC_BACKUP_UPLOAD 1450
-
-
-GNUNET_NETWORK_STRUCT_BEGIN
-
-/**
- * @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_RsaPubHashP h_rsa;
-
- /**
- * 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;
-
-};
-
-
-/**
- * @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;
-
-};
-
-/**
- * @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_DenominationHash h_denom_pub;
-
- /**
- * Transfer public key (for which the private key was not revealed)
- */
- struct TALER_TransferPublicKeyP transfer_pub;
-
- /**
- * Hash of the blinded new coin.
- */
- struct TALER_BlindedCoinHash 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;
-
- /**
- * Hash of the denomination public key for the coin that is withdrawn.
- */
- struct TALER_DenominationHash h_denomination_pub GNUNET_PACKED;
-
- /**
- * Hash of the (blinded) message to be signed by the Exchange.
- */
- struct TALER_BlindedCoinHash 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 TALER_PrivateContractHash h_contract_terms GNUNET_PACKED;
-
- /**
- * Hash over extension attributes shared with the exchange.
- */
- struct TALER_ExtensionContractHash h_extensions GNUNET_PACKED;
-
- /**
- * Hash over the wiring information of the merchant.
- */
- struct TALER_MerchantWireHash h_wire GNUNET_PACKED;
-
- /**
- * Hash over the denomination public key used to sign the coin.
- */
- struct TALER_DenominationHash 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;
-
-};
-
-
-/**
- * @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_PrivateContractHash h_contract_terms GNUNET_PACKED;
-
- /**
- * Hash over the wiring information of the merchant.
- */
- struct TALER_MerchantWireHash h_wire GNUNET_PACKED;
-
- /**
- * Hash over the extension options of the deposit, 0 if there
- * were not extension options.
- */
- struct TALER_ExtensionContractHash h_extensions 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 amount_without_fee;
-
- /**
- * The public key of the coin that was deposited.
- */
- 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_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_PrivateContractHash 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;
-};
-
-
-/**
- * @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_PrivateContractHash 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;
-};
-
-
-/**
- * @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_DenominationHash h_denom_pub 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;
-};
-
-
-/**
- * @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;
-
- /**
- * 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;
-};
-
-
-/**
- * @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;
-};
-
-
-/**
- * @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_PaytoHash h_payto;
-
- /**
- * When was the signature made.
- */
- struct GNUNET_TIME_TimestampNBO timestamp;
-};
-
-
-/**
- * @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;
-};
-
-
-/**
- * @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;
-
-};
-
-
-/**
- * @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_PaytoHash h_payto GNUNET_PACKED;
-};
-
-
-/**
- * @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_PaytoHash h_payto GNUNET_PACKED;
-
-};
-
-
-/*
- * @brief Signature made by the exchange offline key over the
- * configuration of an extension.
- */
-struct TALER_MasterExtensionConfigurationPS
-{
- /**
- * 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 configuration of an extension.
- */
- struct TALER_ExtensionConfigHash h_config 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_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;
-
- /**
- * 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 TALER_DenominationHash 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.
- */
- 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;
-
- /**
- * 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 TALER_DenominationHash 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.
- */
- struct TALER_PaytoHash h_wire_details GNUNET_PACKED;
-
-};
-
-
-/**
- * @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_exchange_wire_signature_hash().
- */
- struct TALER_MerchantWireHash 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_TimestampNBO start_date;
-
- /**
- * End date when the fee stops being in effect (exclusive)
- */
- struct GNUNET_TIME_TimestampNBO 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 TALER_DenominationHash h_denom_pub;
-
-};
-
-
-/**
- * @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;
-
-};
-
-
-/**
- * @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_PrivateContractHash h_contract_terms GNUNET_PACKED;
-
- /**
- * Hash over the wiring information of the merchant.
- */
- struct TALER_MerchantWireHash 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 TALER_PrivateContractHash 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;
-
-};
-
-
-/**
- * @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_PaytoHash h_payto;
-
- /**
- * 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 TALER_PrivateContractHash hash;
-};
-
-/**
- * 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_PrivateContractHash 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 TALER_MerchantWireHash h_wire GNUNET_PACKED;
-
- /**
- * Hash over the contract for which this deposit is made.
- */
- struct TALER_PrivateContractHash 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;
-
-};
-
-
-/**
- * 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_DenominationHash h_denom_pub;
-
- /**
- * Blinding factor that was used to withdraw the coin.
- */
- union 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_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;
-};
-
-
-/**
- * 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;
-};
-
-
-/**
- * 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_DenominationHash h_denom_pub;
-};
-
-
-/**
- * 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_DenominationHash h_denom_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_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 received the recoup.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Hash of the receiver's bank account.
- */
- struct TALER_PaytoHash h_payto;
-
- /**
- * Wire transfer subject.
- */
- struct TALER_WireTransferIdentifierRawP wtid;
-};
-
-
-/**
- * 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_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 20e3145f0..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,155 +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;
-};
+ /**
+ * Base URL of the auditor.
+ */
+ char *auditor_url;
-struct TALER_TESTING_LibeufinServices
-{
/**
- * Nexus
+ * RFC 8905 URI of the exchange.
*/
- struct GNUNET_OS_Process *nexus;
+ char *exchange_payto;
/**
- * Sandbox
+ * RFC 8905 URI of a user.
*/
- struct GNUNET_OS_Process *sandbox;
+ char *user42_payto;
+ /**
+ * RFC 8905 URI of a user.
+ */
+ char *user43_payto;
};
-/**
- * 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
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_prepare_exchange (const char *config_filename,
- int reset_db,
- struct TALER_TESTING_ExchangeConfiguration *ec);
-
-
-/**
- * "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 hr http response details
- * @param keys the exchange's keys.
- * @param compat protocol compatibility information.
- */
-void
-TALER_TESTING_cert_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_EXCHANGE_Keys *keys,
- enum TALER_EXCHANGE_VersionCompatibility compat);
-
/**
- * 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 an HTTPD service 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 exchange
- * 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_httpd_ready (const char *base_url);
-
-
-/**
- * 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);
+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);
/**
@@ -214,65 +219,17 @@ TALER_TESTING_cleanup_files_cfg (void *cls,
/**
- * Run `taler-exchange-offline`.
- *
- * @param config_filename configuration file to use
- * @param payto_uri bank account to enable, can be NULL
- * @param auditor_pub public key of auditor to enable, can be NULL
- * @param auditor_url URL of auditor to enable, can be NULL
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_run_exchange_offline (const char *config_filename,
- const char *payto_uri,
- const char *auditor_pub,
- const char *auditor_url);
-
-
-/**
- * Run `taler-auditor-dbinit -r` (reset auditor database).
- *
- * @param config_filename configuration file to use
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-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
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_exchange_db_reset (const char *config_filename);
-
-
-/**
- * Run `taler-auditor-offline` tool.
- *
- * @param config_filename configuration file to use
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TALER_TESTING_run_auditor_offline (const char *config_filename);
-
-
-/**
- * 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
*/
-enum GNUNET_GenericReturnValue
-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);
/**
@@ -285,194 +242,13 @@ 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
- */
-enum GNUNET_GenericReturnValue
-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;
-
- /**
- * Public key of the auditor.
- */
- struct TALER_AuditorPublicKeyP auditor_pub;
-
- /**
- * Private key of the auditor.
- */
- struct TALER_AuditorPrivateKeyP auditor_priv;
-
- /**
- * Private offline signing key.
- */
- struct TALER_MasterPrivateKeyP master_priv;
-
- /**
- * Public offline signing key.
- */
- struct TALER_MasterPublicKeyP master_pub;
-
- /**
- * URL of the auditor (as per configuration).
- */
- char *auditor_url;
-
- /**
- * URL of the exchange (as per configuration).
- */
- char *exchange_url;
-
- /**
- * 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;
/**
@@ -493,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,
@@ -502,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);
/**
@@ -576,7 +357,41 @@ TALER_TESTING_interpreter_lookup_command (struct TALER_TESTING_Interpreter *is,
/**
- * 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.
@@ -597,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.
@@ -621,14 +427,6 @@ TALER_TESTING_interpreter_next (struct TALER_TESTING_Interpreter *is);
void
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);
-
/**
* Make the instruction pointer point to @a target_label
@@ -685,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
@@ -713,233 +497,197 @@ 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
*/
enum GNUNET_GenericReturnValue
-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);
+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
*/
-enum GNUNET_GenericReturnValue
-TALER_TESTING_auditor_setup (TALER_TESTING_Main main_cb,
- void *main_cb_cls,
- const char *config_filename);
+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);
/**
- * 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;
-
- /**
- * Closure for @e main_cb.
- */
- void *main_cb_cls;
-
- /**
- * Name of the configuration file.
- */
- const char *config_filename;
-};
+typedef void
+(*TALER_TESTING_CommandIterator)(
+ void *cls,
+ const struct TALER_TESTING_Command *cmd);
/**
- * Initialize scheduler loop and curl context for the test case
- * including starting and stopping the exchange using the given
- * configuration file.
+ * Iterates over all of the top-level commands of an
+ * interpreter.
*
- * @param cls must be a `struct TALER_TESTING_SetupContext *`
- * @param cfg configuration to use.
- * @return #GNUNET_OK if no errors occurred.
+ * @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
*/
-enum GNUNET_GenericReturnValue
-TALER_TESTING_setup_with_exchange_cfg (
- void *cls,
- const struct GNUNET_CONFIGURATION_Handle *cfg);
+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 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 prog program's name to look into
+ * @param marker chunk to find in @a prog
+ * @return true if @a marker is in @a prog
*/
-enum GNUNET_GenericReturnValue
-TALER_TESTING_setup_with_exchange (TALER_TESTING_Main main_cb,
- void *main_cb_cls,
- const char *config_file);
+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 auditor and 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 cls must be a `struct TALER_TESTING_SetupContext *`
- * @param cfg configuration to use.
- * @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
*/
-enum GNUNET_GenericReturnValue
-TALER_TESTING_setup_with_auditor_and_exchange_cfg (
- void *cls,
- const struct GNUNET_CONFIGURATION_Handle *cfg);
+int
+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 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 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
*/
enum GNUNET_GenericReturnValue
-TALER_TESTING_setup_with_auditor_and_exchange (TALER_TESTING_Main main_cb,
- void *main_cb_cls,
- const char *config_file);
+TALER_TESTING_parse_coin_reference (
+ const char *coin_reference,
+ char **cref,
+ unsigned int *idx);
+
+
+/* ************** Specific interpreter commands ************ */
/**
- * Start the (Python) bank process. Assume the port
- * is available and the database is clean. Use the "prepare
- * bank" function to do such tasks.
+ * Create command array terminator.
*
- * @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.
+ * @return a end-command.
*/
-struct GNUNET_OS_Process *
-TALER_TESTING_run_bank (const char *config_filename,
- const char *bank_url);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_end (void);
+
/**
- * Start the (nexus) bank process. Assume the port
- * is available and the database is clean. Use the "prepare
- * bank" function to do such tasks. This function is also
- * responsible to create the exchange EBICS subscriber at
- * the nexus.
+ * Set variable to command as side-effect of
+ * running a command.
*
- * @param bc bank configuration of the bank
- * @return the process, or NULL if the process could not
- * be started.
+ * @param name name of the variable to set
+ * @param cmd command to set to variable when run
+ * @return modified command
*/
-struct TALER_TESTING_LibeufinServices
-TALER_TESTING_run_libeufin (const struct TALER_TESTING_BankConfiguration *bc);
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_var (const char *name,
+ struct TALER_TESTING_Command cmd);
/**
- * Runs the Fakebank by guessing / extracting the portnumber
- * from the base URL.
+ * Launch GNU Taler setup.
*
- * @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 config_file configuration file to use
+ * @param ... NULL-terminated (const char *) arguments to pass to taler-benchmark-setup.sh
+ * @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_system_start (
+ const char *label,
+ const char *config_file,
+ ...);
/**
- * Prepare the bank execution. Check if the port is available
- * and reset database.
+ * Connects to the exchange.
*
- * @param config_filename configuration file name.
- * @param reset_db should we reset the bank's database
- * @param config_section which configuration section should be used
- * @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 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.
*/
-enum GNUNET_GenericReturnValue
-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_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 Nexus execution. Check if the port is available
- * and delete old database.
+ * Connects to the auditor.
*
- * @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.
+ * @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.
*/
-enum GNUNET_GenericReturnValue
-TALER_TESTING_prepare_nexus (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.
*/
-enum GNUNET_GenericReturnValue
-TALER_TESTING_has_in_name (const char *prog,
- const char *marker);
-
-
-/* ************** Specific interpreter commands ************ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_run_fakebank (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *exchange_account_section);
/**
@@ -998,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.
@@ -1055,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);
/**
@@ -1084,48 +829,7 @@ 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);
-
-
-/**
- * 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);
+ struct TALER_TESTING_Command cmd);
/**
@@ -1195,6 +899,56 @@ TALER_TESTING_cmd_exec_wirewatch (const char *label,
/**
+ * 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);
+
+
+/**
* Run a "taler-exchange-aggregator" CMD.
*
* @param label command label.
@@ -1277,6 +1031,7 @@ TALER_TESTING_cmd_exec_transfer (const char *label,
* @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 expected_response_code which HTTP response code
* we expect from the exchange.
* @return the withdraw command to be executed by the interpreter.
@@ -1285,10 +1040,107 @@ 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);
/**
+ * 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 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_batch_withdraw_with_conflict (
+ const char *label,
+ const char *reserve_reference,
+ bool conflict,
+ uint8_t age,
+ unsigned int expected_response_code,
+ const char *amount,
+ ...);
+
+/**
+ * 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 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_age_withdraw (const char *label,
+ const char *reserve_reference,
+ uint8_t max_age,
+ unsigned int expected_response_code,
+ const char *amount,
+ ...);
+
+/**
+ * Create a "age-withdraw reveal" command.
+ *
+ * @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_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 and also re-using an existing
* coin private key in the process (violating the specification,
@@ -1297,8 +1149,9 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
* @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 re-use the private key
+ * 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.
@@ -1308,6 +1161,7 @@ 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);
@@ -1344,47 +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);
+
/**
- * Index of the deposit value trait of a deposit command.
+ * 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.
*/
-#define TALER_TESTING_CMD_DEPOSIT_TRAIT_IDX_DEPOSIT_VALUE 0
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_poll_finish (const char *label,
+ struct GNUNET_TIME_Relative timeout,
+ const char *poll_reference);
+
/**
- * Index of the deposit fee trait of a deposit command.
+ * 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.
*/
-#define TALER_TESTING_CMD_DEPOSIT_TRAIT_IDX_DEPOSIT_FEE 1
+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.
@@ -1477,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.
@@ -1709,14 +1696,13 @@ TALER_TESTING_cmd_check_bank_empty (const char *label);
* 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 *deposit_reference,
+ const char *coin_reference,
uint64_t refund_transaction_id);
@@ -1728,14 +1714,13 @@ TALER_TESTING_cmd_refund_with_id (const char *label,
* @param refund_amount the amount to ask a refund for.
* @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 *deposit_reference);
+ const char *coin_reference);
/**
@@ -1834,60 +1819,6 @@ struct TALER_TESTING_Command
TALER_TESTING_cmd_wait_service (const char *label,
const char *url);
-
-/**
- * Make a "check keys" command.
- *
- * @param label command label
- * @param generation how many /keys responses are expected to
- * have been returned when this CMD will be run.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys (const char *label,
- unsigned int generation);
-
-
-/**
- * Make a "check keys" command that forcedly does NOT cherry pick;
- * just redownload the whole /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.
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys_pull_all_keys (const char *label,
- unsigned int generation);
-
-
-/**
- * Make a "check keys" command. 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 last_denom_date_ref previous /keys command to use to
- * obtain the "last_denom_date" value from; "zero" can be used
- * as a special value to force an absolute time of zero to be
- * given to as an argument
- * @return the command.
- */
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys_with_last_denom (
- const char *label,
- unsigned int generation,
- const char *last_denom_date_ref);
-
-
/**
* Create a "batch" command. Such command takes a
* end_CMD-terminated array of CMDs and executed them.
@@ -1913,13 +1844,18 @@ TALER_TESTING_cmd_batch (const char *label,
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.
@@ -1942,34 +1878,10 @@ TALER_TESTING_cmd_batch_set_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.
- *
- * @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);
-
-/**
* 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
@@ -1982,7 +1894,7 @@ TALER_TESTING_cmd_connect_with_state (const char *label,
struct TALER_TESTING_Command
TALER_TESTING_cmd_insert_deposit (
const char *label,
- const struct TALER_TESTING_DatabaseConnection *dbc,
+ const struct GNUNET_CONFIGURATION_Handle *db_cfg,
const char *merchant_name,
const char *merchant_account,
struct GNUNET_TIME_Timestamp exchange_timestamp,
@@ -2007,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;
@@ -2084,6 +1996,7 @@ TALER_TESTING_cmd_auditor_add_denom_sig (const char *label,
const char *denom_ref,
bool bad_sig);
+
/**
* Add statement about wire fees of the exchange. This is always
* done for a few hours around the current time (for the test).
@@ -2138,6 +2051,19 @@ TALER_TESTING_cmd_wire_del (const char *label,
unsigned int expected_http_status,
bool bad_sig);
+/**
+ * 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 label command label.
+ * @param config_filename configuration filename.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label,
+ const char *config_filename);
+
/**
* Sign all exchange denomination and online signing keys
@@ -2155,7 +2081,7 @@ TALER_TESTING_cmd_exec_offline_sign_keys (const char *label,
/**
- * Sign a wire fee.
+ * Sign a wire fee structure.
*
* @param label command label.
* @param config_filename configuration filename.
@@ -2171,6 +2097,31 @@ TALER_TESTING_cmd_exec_offline_sign_fees (const char *label,
/**
+ * Sign global fee structure.
+ *
+ * @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
+ */
+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);
+
+
+/**
* Revoke an exchange denomination key.
*
* @param label command label.
@@ -2209,36 +2160,18 @@ TALER_TESTING_cmd_revoke_sign_key (
/**
- * Have the auditor affirm that it is auditing the given
- * denomination key and upload the auditor's signature to
- * the exchange.
- *
- * @param label command label.
- * @param expected_http_status expected HTTP status from exchange
- * @param denom_ref reference to a command that identifies
- * a denomination key (i.e. because it was used to
- * withdraw a coin).
- * @param bad_sig should we use a bogus signature?
- * @return the command
- */
-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);
-
-
-/**
* Create a request for a wallet's KYC UUID.
*
* @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_Command
TALER_TESTING_cmd_wallet_kyc_get (const char *label,
const char *reserve_reference,
+ const char *threshold_balance,
unsigned int expected_response_code);
@@ -2257,22 +2190,38 @@ TALER_TESTING_cmd_check_kyc_get (const char *label,
/**
- * Create a KYC proof request.
+ * Create a KYC proof request. Only useful in conjunction with the OAuth2.0
+ * logic, as it generates an OAuth2.0-specific request.
*
* @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 state OAuth 2.0 state to use
* @param expected_response_code expected HTTP status
* @return the command
*/
struct TALER_TESTING_Command
-TALER_TESTING_cmd_proof_kyc (const char *label,
- const char *payment_target_reference,
- const char *code,
- const char *state,
- unsigned int expected_response_code);
+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);
+
+/**
+ * Starts a fake OAuth 2.0 service on @a port for testing
+ * KYC processes which also provides a @a birthdate in a response
+ *
+ * @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_Command
+TALER_TESTING_cmd_oauth_with_birthdate (const char *label,
+ const char *birthdate,
+ uint16_t port);
/**
* Starts a fake OAuth 2.0 service on @a port for testing
@@ -2281,9 +2230,267 @@ TALER_TESTING_cmd_proof_kyc (const char *label,
* @param label command label
* @param port the TCP port to listen on
*/
+#define TALER_TESTING_cmd_oauth(label, port) \
+ TALER_TESTING_cmd_oauth_with_birthdate ((label), NULL, (port))
+
+
+/* ****************** P2P payment commands ****************** */
+
+
+/**
+ * Creates a purse with deposits.
+ *
+ * @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_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,
+ ...);
+
+
+/**
+ * Deletes a purse.
+ *
+ * @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_Command
+TALER_TESTING_cmd_purse_delete (
+ const char *label,
+ unsigned int expected_http_status,
+ const char *purse_cmd);
+
+
+/**
+ * Retrieve contract (also checks that the contract matches
+ * the upload command).
+ *
+ * @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_Command
+TALER_TESTING_cmd_contract_get (
+ const char *label,
+ unsigned int expected_http_status,
+ bool for_merge,
+ const char *contract_ref);
+
+
+/**
+ * Retrieve purse state by merge private key.
+ *
+ * @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
+ */
struct TALER_TESTING_Command
-TALER_TESTING_cmd_oauth (const char *label,
- uint16_t port);
+TALER_TESTING_cmd_purse_merge (
+ const char *label,
+ unsigned int expected_http_status,
+ const char *merge_ref,
+ const char *reserve_ref);
+
+
+/**
+ * Retrieve purse state.
+ *
+ * @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
+ */
+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);
+
+
+/**
+ * Wait for the poll command to complete.
+ *
+ * @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_Command
+TALER_TESTING_cmd_purse_poll_finish (const char *label,
+ struct GNUNET_TIME_Relative timeout,
+ const char *poll_reference);
+
+
+/**
+ * Creates a purse with reserve.
+ *
+ * @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
+ */
+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);
+
+
+/**
+ * Deposit coins into a purse.
+ *
+ * @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_Command
+TALER_TESTING_cmd_purse_deposit_coins (
+ const char *label,
+ unsigned int expected_http_status,
+ uint8_t min_age,
+ const char *purse_ref,
+ ...);
+
+
+/**
+ * Setup AML officer.
+ *
+ * @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
+ */
+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);
+
+
+/**
+ * Make AML decision.
+ *
+ * @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_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);
+
+
+/**
+ * Fetch AML decision.
+ *
+ * @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
+ */
+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);
+
+
+/**
+ * Fetch AML decisions.
+ *
+ * @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_Command
+TALER_TESTING_cmd_check_aml_decisions (
+ const char *label,
+ const char *ref_officer,
+ enum TALER_AmlDecisionState filter,
+ unsigned int expected_http_status);
+
+
+/* ****************** convenience functions ************** */
+
+/**
+ * Get exchange URL from interpreter. Convenience function.
+ *
+ * @param is interpreter state.
+ * @return the exchange URL, or NULL on error
+ */
+const char *
+TALER_TESTING_get_exchange_url (
+ struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Get exchange keys from interpreter. Convenience function.
+ *
+ * @param is interpreter state.
+ * @return the exchange keys, or NULL on error
+ */
+struct TALER_EXCHANGE_Keys *
+TALER_TESTING_get_keys (
+ struct TALER_TESTING_Interpreter *is);
/* *** Generic trait logic for implementing traits ********* */
@@ -2442,56 +2649,85 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
*/
#define TALER_TESTING_SIMPLE_TRAITS(op) \
op (bank_row, const uint64_t) \
- op (reserve_priv, const struct TALER_ReservePrivateKeyP) \
- op (reserve_pub, const struct TALER_ReservePublicKeyP) \
+ 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_keys, const json_t) \
- op (reserve_history, const struct TALER_EXCHANGE_ReserveHistory) \
- op (exchange_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 (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 (payment_target_uuid, 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 (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 (deposit_amount, const struct TALER_Amount) \
- op (deposit_fee_amount, const struct TALER_Amount) \
- op (batch_cmds, struct TALER_TESTING_Command *) \
+ 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 (coin_priv, const struct TALER_CoinSpendPrivateKeyP) \
- op (coin_pub, const struct TALER_CoinSpendPublicKeyP) \
- 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 TALER_DenominationBlindingKeyP)
-
+#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)
diff --git a/src/include/taler_util.h b/src/include/taler_util.h
index 8d7f5f754..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-2021 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,11 +16,17 @@
/**
* @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"
@@ -30,7 +36,7 @@
* Version of the Taler API, in hex.
* Thus 0.8.4-1 = 0x00080401.
*/
-#define TALER_API_VERSION 0x00080401
+#define TALER_API_VERSION 0x00090401
/**
* Stringify operator.
@@ -79,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).
@@ -125,12 +147,13 @@ 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
@@ -144,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").
*
@@ -157,6 +213,101 @@ TALER_config_get_currency (const struct GNUNET_CONFIGURATION_Handle *cfg,
/**
+ * 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
@@ -188,6 +339,16 @@ 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
@@ -209,6 +370,17 @@ 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
@@ -244,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
@@ -328,16 +501,17 @@ TALER_payto_get_method (const char *payto_uri);
/**
- * Construct a payto://-URI from a Taler @a reserve_pub at
- * a @exchange_base_url
+ * 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 exchange_base_url the URL of the exchange
- * @param reserve_pub public key of the reserve
- * @return payto:// URI encoding the reserve's address
+ * @param input a payto://-URI
+ * @return normalized URI, or NULL if @a input was not well-formed
*/
char *
-TALER_payto_from_reserve (const char *exchange_base_url,
- const struct TALER_ReservePublicKeyP *reserve_pub);
+TALER_payto_normalize (const char *input);
/**
@@ -352,6 +526,16 @@ 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
@@ -374,6 +558,19 @@ 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:
@@ -435,4 +632,198 @@ 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 2f5ec3f17..ce863cb7e 100644
--- a/src/json/Makefile.am
+++ b/src/json/Makefile.am
@@ -16,7 +16,7 @@ libtalerjson_la_SOURCES = \
json_pack.c \
json_wire.c
libtalerjson_la_LDFLAGS = \
- -version-info 1:0:1 \
+ -version-info 3:0:1 \
-no-undefined
libtalerjson_la_LIBADD = \
$(top_builddir)/src/util/libtalerutil.la \
@@ -28,12 +28,10 @@ libtalerjson_la_LIBADD = \
$(XLIB)
TESTS = \
- test_json \
- test_json_wire
+ test_json
check_PROGRAMS= \
- test_json \
- test_json_wire
+ test_json
test_json_SOURCES = \
test_json.c
@@ -43,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/json.c b/src/json/json.c
index 956aad1a5..639bd530c 100644
--- a/src/json/json.c
+++ b/src/json/json.c
@@ -62,161 +62,6 @@ contains_real (const json_t *json)
/**
- * 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;
- }
-}
-
-
-/**
- * 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
- */
-static size_t
-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) )
- {
- 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;
- }
- }
- 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;
-}
-
-
-/**
* Dump the @a json to a string and hash it.
*
* @param json value to hash
@@ -253,7 +98,7 @@ dump_and_hash (const json_t *json,
GNUNET_break (0);
return GNUNET_SYSERR;
}
- len = rfc8785encode (&wire_enc);
+ len = TALER_rfc8785encode (&wire_enc);
if (NULL == salt)
{
GNUNET_CRYPTO_hash (wire_enc,
@@ -481,7 +326,7 @@ forget (const json_t *in,
enum GNUNET_GenericReturnValue
TALER_JSON_contract_hash (const json_t *json,
- struct TALER_PrivateContractHash *hc)
+ struct TALER_PrivateContractHashP *hc)
{
enum GNUNET_GenericReturnValue ret;
json_t *cjson;
@@ -688,7 +533,7 @@ TALER_JSON_contract_part_forget (json_t *json,
/**
- * Look over all of the values of a '$forgettable' object. Replace 'True'
+ * 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).
*
@@ -733,51 +578,64 @@ seed_forgettable (json_t *f)
}
-/**
- * Take a given contract with "forgettable" fields marked
- * but with 'True' instead of a real salt. Replaces all
- * 'True' values with proper random salts. Fails if any
- * forgettable markers are neither 'True' nor valid salts.
- *
- * @param[in,out] json JSON to transform
- * @return #GNUNET_OK on success
- */
enum GNUNET_GenericReturnValue
-TALER_JSON_contract_seed_forgettable (json_t *json)
+TALER_JSON_contract_seed_forgettable (const json_t *spec,
+ json_t *contract)
{
- if (json_is_object (json))
+ if (json_is_object (spec))
{
const char *key;
json_t *val;
- json_object_foreach (json,
+ 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 (val))
+ 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))
+ TALER_JSON_contract_seed_forgettable (val,
+ cval))
return GNUNET_SYSERR;
}
}
- if (json_is_array (json))
+ if (json_is_array (spec))
{
size_t index;
json_t *val;
- json_array_foreach (json,
+ 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))
+ TALER_JSON_contract_seed_forgettable (val,
+ ival))
return GNUNET_SYSERR;
}
}
@@ -810,6 +668,7 @@ parse_path (json_t *obj,
json_t *next_obj = NULL;
char *next_dot;
+ GNUNET_assert (NULL != id); /* make stupid compiler happy */
if (NULL == next_id)
{
cb (cb_cls,
@@ -999,22 +858,40 @@ TALER_JSON_get_error_code2 (const void *data,
void
-TALER_deposit_extension_hash (const json_t *extensions,
- struct TALER_ExtensionContractHash *ech)
+TALER_deposit_policy_hash (const json_t *policy,
+ struct TALER_ExtensionPolicyHashP *ech)
{
GNUNET_assert (GNUNET_OK ==
- dump_and_hash (extensions,
- "taler-contract-extensions",
+ 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_extension_config_hash (const json_t *config,
- struct TALER_ExtensionConfigHash *ech)
+TALER_JSON_extensions_manifests_hash (const json_t *manifests,
+ struct TALER_ExtensionManifestsHashP *ech)
{
- return dump_and_hash (config,
- "taler-extension-configuration",
+ return dump_and_hash (manifests,
+ "taler-extensions-manifests",
&ech->hash);
}
diff --git a/src/json/json_helper.c b/src/json/json_helper.c
index 3b4da5595..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-2021 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
@@ -26,6 +26,29 @@
#include "taler_json_lib.h"
+/**
+ * Convert string value to numeric cipher value.
+ *
+ * @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)
{
@@ -41,17 +64,6 @@ TALER_JSON_from_amount (const struct TALER_Amount *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
*
@@ -87,6 +99,11 @@ parse_amount (void *cls,
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;
@@ -132,7 +149,7 @@ TALER_JSON_spec_amount_any (const char *name,
/**
- * 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
@@ -140,75 +157,375 @@ TALER_JSON_spec_amount_any (const char *name,
* @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
*/
static enum GNUNET_GenericReturnValue
-parse_amount_nbo (void *cls,
- json_t *root,
- struct GNUNET_JSON_Specification *spec)
+parse_cspec (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
{
- const char *currency = cls;
- struct TALER_AmountNBO *r_amount = spec->ptr;
- const char *sv;
+ 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;
- (void) cls;
- if (! json_is_string (root))
+ 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 (0);
+ GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- sv = json_string_value (root);
if (GNUNET_OK !=
- TALER_string_to_amount_nbo (sv,
- r_amount))
+ TALER_check_currency (currency))
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "`%s' is not a valid amount\n",
- sv);
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if ( (NULL != currency) &&
- (0 !=
- strcasecmp (currency,
- r_amount->currency)) )
+ 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;
+ GNUNET_free (cspec->name);
+ json_decref (cspec->map_alt_unit_names);
+}
+
+
struct GNUNET_JSON_Specification
-TALER_JSON_spec_amount_nbo (const char *name,
- const char *currency,
- struct TALER_AmountNBO *r_amount)
+TALER_JSON_spec_currency_specification (
+ const char *name,
+ const char *currency,
+ struct TALER_CurrencySpecification *r_cspec)
{
struct GNUNET_JSON_Specification ret = {
- .parser = &parse_amount_nbo,
- .cleaner = NULL,
+ .parser = &parse_cspec,
+ .cleaner = &clean_cspec,
.cls = (void *) currency,
.field = name,
- .ptr = r_amount,
- .ptr_size = 0,
+ .ptr = r_cspec,
+ .ptr_size = sizeof (*r_cspec),
.size_ptr = NULL
};
- GNUNET_assert (NULL != currency);
+ 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_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (age_mask_missing)
+ group->age_mask.bits = 0;
+
+ return GNUNET_OK;
+}
+
+
struct GNUNET_JSON_Specification
-TALER_JSON_spec_amount_any_nbo (const char *name,
- struct TALER_AmountNBO *r_amount)
+TALER_JSON_spec_denomination_group (const char *name,
+ const char *currency,
+ struct TALER_DenominationGroup *group)
{
struct GNUNET_JSON_Specification ret = {
- .parser = &parse_amount_nbo,
- .cleaner = NULL,
- .cls = NULL,
+ .cls = (void *) currency,
+ .parser = &parse_denomination_group,
.field = name,
- .ptr = r_amount,
- .ptr_size = 0,
- .size_ptr = NULL
+ .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 !=
+ GNUNET_JSON_parse (root,
+ ispec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ 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_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_econtract (const char *name,
+ struct TALER_EncryptedContract *econtract)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_econtract,
+ .cleaner = &clean_econtract,
+ .field = name,
+ .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;
@@ -229,12 +546,16 @@ parse_denom_pub (void *cls,
struct GNUNET_JSON_Specification *spec)
{
struct TALER_DenominationPublicKey *denom_pub = spec->ptr;
- uint32_t cipher;
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bsign_pub;
+ const char *cipher;
+ bool age_mask_missing = false;
struct GNUNET_JSON_Specification dspec[] = {
- GNUNET_JSON_spec_uint32 ("cipher",
+ GNUNET_JSON_spec_string ("cipher",
&cipher),
- GNUNET_JSON_spec_uint32 ("age_mask",
- &denom_pub->age_mask.mask),
+ 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;
@@ -250,15 +571,22 @@ parse_denom_pub (void *cls,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- denom_pub->cipher = (enum TALER_DenominationCipher) cipher;
- switch (denom_pub->cipher)
+
+ 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 TALER_DENOMINATION_RSA:
+ 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",
- &denom_pub->details.rsa_public_key),
+ &bsign_pub->details.rsa_public_key),
GNUNET_JSON_spec_end ()
};
@@ -269,14 +597,38 @@ parse_denom_pub (void *cls,
&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;
}
- default:
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
}
+ GNUNET_break_op (0);
+ GNUNET_free (bsign_pub);
+ return GNUNET_SYSERR;
}
@@ -308,6 +660,105 @@ TALER_JSON_spec_denom_pub (const char *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;
}
@@ -326,9 +777,10 @@ parse_denom_sig (void *cls,
struct GNUNET_JSON_Specification *spec)
{
struct TALER_DenominationSignature *denom_sig = spec->ptr;
- uint32_t cipher;
+ struct GNUNET_CRYPTO_UnblindedSignature *unblinded_sig;
+ const char *cipher;
struct GNUNET_JSON_Specification dspec[] = {
- GNUNET_JSON_spec_uint32 ("cipher",
+ GNUNET_JSON_spec_string ("cipher",
&cipher),
GNUNET_JSON_spec_end ()
};
@@ -345,15 +797,19 @@ parse_denom_sig (void *cls,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- denom_sig->cipher = (enum TALER_DenominationCipher) cipher;
- switch (denom_sig->cipher)
+ unblinded_sig = GNUNET_new (struct GNUNET_CRYPTO_UnblindedSignature);
+ unblinded_sig->cipher = string_to_cipher (cipher);
+ unblinded_sig->rc = 1;
+ switch (unblinded_sig->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
{
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_rsa_signature (
"rsa_signature",
- &denom_sig->details.rsa_signature),
+ &unblinded_sig->details.rsa_signature),
GNUNET_JSON_spec_end ()
};
@@ -364,14 +820,41 @@ parse_denom_sig (void *cls,
&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;
}
- default:
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
}
+ GNUNET_break_op (0);
+ GNUNET_free (unblinded_sig);
+ return GNUNET_SYSERR;
}
@@ -403,6 +886,7 @@ TALER_JSON_spec_denom_sig (const char *field,
.ptr = sig
};
+ sig->unblinded_sig = NULL;
return ret;
}
@@ -421,9 +905,10 @@ parse_blinded_denom_sig (void *cls,
struct GNUNET_JSON_Specification *spec)
{
struct TALER_BlindedDenominationSignature *denom_sig = spec->ptr;
- uint32_t cipher;
+ struct GNUNET_CRYPTO_BlindedSignature *blinded_sig;
+ const char *cipher;
struct GNUNET_JSON_Specification dspec[] = {
- GNUNET_JSON_spec_uint32 ("cipher",
+ GNUNET_JSON_spec_string ("cipher",
&cipher),
GNUNET_JSON_spec_end ()
};
@@ -440,15 +925,19 @@ parse_blinded_denom_sig (void *cls,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- denom_sig->cipher = (enum TALER_DenominationCipher) cipher;
- switch (denom_sig->cipher)
+ blinded_sig = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature);
+ blinded_sig->cipher = string_to_cipher (cipher);
+ blinded_sig->rc = 1;
+ switch (blinded_sig->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
{
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_rsa_signature (
"blinded_rsa_signature",
- &denom_sig->details.blinded_rsa_signature),
+ &blinded_sig->details.blinded_rsa_signature),
GNUNET_JSON_spec_end ()
};
@@ -459,14 +948,40 @@ parse_blinded_denom_sig (void *cls,
&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;
}
- default:
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
}
+ GNUNET_break_op (0);
+ GNUNET_free (blinded_sig);
+ return GNUNET_SYSERR;
}
@@ -499,6 +1014,253 @@ TALER_JSON_spec_blinded_denom_sig (
.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;
}
@@ -575,11 +1337,6 @@ parse_i18n_string (void *cls,
const char *str;
str = json_string_value (val);
- if (NULL == str)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
*(const char **) spec->ptr = str;
}
return GNUNET_OK;
@@ -599,8 +1356,11 @@ i18n_cleaner (void *cls,
struct I18nContext *ctx = cls;
(void) spec;
- GNUNET_free (ctx->lp);
- GNUNET_free (ctx);
+ if (NULL != ctx)
+ {
+ GNUNET_free (ctx->lp);
+ GNUNET_free (ctx);
+ }
}
@@ -620,8 +1380,9 @@ TALER_JSON_spec_i18n_string (const char *name,
.size_ptr = NULL
};
- ctx->lp = (NULL != language_pattern) ? GNUNET_strdup (language_pattern) :
- NULL;
+ ctx->lp = (NULL != language_pattern)
+ ? GNUNET_strdup (language_pattern)
+ : NULL;
ctx->field = name;
*strptr = NULL;
return ret;
@@ -659,36 +1420,400 @@ TALER_JSON_spec_i18n_str (const char *name,
}
-enum GNUNET_GenericReturnValue
-TALER_agemask_parse_json (const json_t *root,
- struct TALER_AgeMask *mask)
+/**
+ * Parse given JSON object with Taler error code.
+ *
+ * @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)
{
- const char *name;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("extension",
- &name),
- GNUNET_JSON_spec_uint32 ("mask",
- &mask->mask),
- GNUNET_JSON_spec_end ()
+ 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_ec (const char *field,
+ enum TALER_ErrorCode *ec)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .parser = &parse_ec,
+ .field = field,
+ .ptr = ec
};
- if (GNUNET_OK != GNUNET_JSON_parse (root,
- spec,
- NULL,
- NULL))
+ *ec = TALER_EC_NONE;
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object with AML decision.
+ *
+ * @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;
+}
+
- if (! strncmp (name,
- "age_restriction",
- sizeof("age_restriction")))
+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_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)
+{
+ 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;
+}
+
+
/* end of json/json_helper.c */
diff --git a/src/json/json_pack.c b/src/json/json_pack.c
index 6fea72a39..71c8db9d2 100644
--- a/src/json/json_pack.c
+++ b/src/json/json_pack.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021, 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -39,11 +39,60 @@ TALER_JSON_pack_time_abs_human (const char *name,
struct GNUNET_JSON_PackSpec
-TALER_JSON_pack_time_abs_nbo_human (const char *name,
- struct GNUNET_TIME_AbsoluteNBO at)
+TALER_JSON_pack_econtract (
+ const char *name,
+ const struct TALER_EncryptedContract *econtract)
{
- return TALER_JSON_pack_time_abs_human (name,
- GNUNET_TIME_absolute_ntoh (at));
+ 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;
}
@@ -52,25 +101,41 @@ 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,
};
- switch (pk->cipher)
+ if (NULL == pk)
+ return ps;
+ bsp = pk->bsign_pub_key;
+ switch (bsp->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
ps.object
= GNUNET_JSON_PACK (
- GNUNET_JSON_pack_uint64 ("cipher",
- TALER_DENOMINATION_RSA),
+ GNUNET_JSON_pack_string ("cipher",
+ "RSA"),
GNUNET_JSON_pack_uint64 ("age_mask",
- pk->age_mask.mask),
+ pk->age_mask.bits),
GNUNET_JSON_pack_rsa_public_key ("rsa_public_key",
- pk->details.rsa_public_key));
- break;
- default:
- GNUNET_assert (0);
+ 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;
}
@@ -80,23 +145,78 @@ 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,
};
- switch (sig->cipher)
+ if (NULL == sig)
+ return ps;
+ bs = sig->unblinded_sig;
+ switch (bs->cipher)
{
- case TALER_DENOMINATION_RSA:
- ps.object
- = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_uint64 ("cipher",
- TALER_DENOMINATION_RSA),
- GNUNET_JSON_pack_rsa_signature ("rsa_signature",
- sig->details.rsa_signature));
+ case GNUNET_CRYPTO_BSA_INVALID:
break;
- default:
- GNUNET_assert (0);
+ 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;
}
@@ -106,50 +226,94 @@ 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,
};
- switch (sig->cipher)
+ if (NULL == sig)
+ return ps;
+ bs = sig->blinded_sig;
+ switch (bs->cipher)
{
- case TALER_DENOMINATION_RSA:
- ps.object
- = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_uint64 ("cipher",
- TALER_DENOMINATION_RSA),
- GNUNET_JSON_pack_rsa_signature ("blinded_rsa_signature",
- sig->details.blinded_rsa_signature));
+ case GNUNET_CRYPTO_BSA_INVALID:
break;
- default:
- GNUNET_assert (0);
+ 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_amount (const char *name,
- const struct TALER_Amount *amount)
+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,
- .object = (NULL != amount)
- ? TALER_JSON_from_amount (amount)
- : NULL
};
+ 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_nbo (const char *name,
- const struct TALER_AmountNBO *amount)
+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_nbo (amount)
+ ? TALER_JSON_from_amount (amount)
: NULL
};
diff --git a/src/json/json_wire.c b/src/json/json_wire.c
index 8f7fd6bb6..9d22d28ea 100644
--- a/src/json/json_wire.c
+++ b/src/json/json_wire.c
@@ -26,10 +26,10 @@
enum GNUNET_GenericReturnValue
TALER_JSON_merchant_wire_signature_hash (const json_t *wire_s,
- struct TALER_MerchantWireHash *hc)
+ struct TALER_MerchantWireHashP *hc)
{
const char *payto_uri;
- struct TALER_WireSalt salt;
+ struct TALER_WireSaltP salt;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("payto_uri",
&payto_uri),
@@ -70,80 +70,6 @@ TALER_JSON_merchant_wire_signature_hash (const json_t *wire_s,
}
-enum GNUNET_GenericReturnValue
-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_break_op (0);
- return GNUNET_SYSERR;
- }
-
- {
- char *err;
-
- 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;
- }
- }
-
- return TALER_exchange_wire_signature_check (payto_uri,
- master_pub,
- &master_sig);
-}
-
-
-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 (NULL !=
- (err = TALER_payto_validate (payto_uri)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid payto URI `%s': %s\n",
- payto_uri,
- err);
- GNUNET_free (err);
- return NULL;
- }
- TALER_exchange_wire_signature_make (payto_uri,
- master_priv,
- &master_sig);
- return GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("payto_uri",
- payto_uri),
- GNUNET_JSON_pack_data_auto ("master_sig",
- &master_sig));
-}
-
-
char *
TALER_JSON_wire_to_payto (const json_t *wire_s)
{
diff --git a/src/json/test_json.c b/src/json/test_json.c
index a8c1c6d8e..fba72f84b 100644
--- a/src/json/test_json.c
+++ b/src/json/test_json.c
@@ -91,8 +91,8 @@ path_cb (void *cls,
static int
test_contract (void)
{
- struct TALER_PrivateContractHash h1;
- struct TALER_PrivateContractHash h2;
+ struct TALER_PrivateContractHashP h1;
+ struct TALER_PrivateContractHashP h2;
json_t *c1;
json_t *c2;
json_t *c3;
@@ -103,7 +103,8 @@ test_contract (void)
"k2", "n1", "n2",
/***/ "$forgettable", "n1", true);
GNUNET_assert (GNUNET_OK ==
- TALER_JSON_contract_seed_forgettable (c1));
+ TALER_JSON_contract_seed_forgettable (c1,
+ c1));
GNUNET_assert (GNUNET_OK ==
TALER_JSON_contract_hash (c1,
&h1));
@@ -160,7 +161,7 @@ test_contract (void)
GNUNET_assert (GNUNET_OK ==
TALER_JSON_contract_part_forget (c1,
"k2"));
- json_dumpf (c1, stderr, JSON_INDENT (2));
+ // json_dumpf (c1, stderr, JSON_INDENT (2));
GNUNET_assert (GNUNET_OK ==
TALER_JSON_contract_hash (c1,
&h2));
@@ -182,7 +183,7 @@ test_contract (void)
GNUNET_assert (GNUNET_OK ==
TALER_JSON_contract_hash (c1,
&h1));
- json_dumpf (c1, stderr, JSON_INDENT (2));
+ // json_dumpf (c1, stderr, JSON_INDENT (2));
json_decref (c1);
{
char *s;
@@ -331,9 +332,60 @@ test_contract (void)
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_PrivateContractHash h1;
+ struct TALER_PrivateContractHashP h1;
json_t *c1;
c1 = json_pack ("{s:s}",
@@ -348,7 +400,7 @@ test_rfc8785 (void)
sizeof (h1));
if (0 !=
strcmp (s,
- "J678K3PW9Y3DG63Z3T7ZYR2P7CEXMVZ2SFPQMABACK9TJRYREPP82542PCJ0P7Y7FAQAMWECDX50XH1RBTWHX6SSJHH6FXRV0JCS6R8"))
+ "531S33T8ZRGW6548G7T67PMDNGS4Z1D8A2GMB87G3PNKYTW6KGF7Q99XVCGXBKVA2HX6PR5ENJ1PQ5ZTYMMXQB6RM7S82VP7ZG2X5G8"))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Invalid reference hash: %s\n",
@@ -377,6 +429,8 @@ main (int argc,
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 b417b25fe..000000000
--- a/src/json/test_json_wire.c
+++ /dev/null
@@ -1,80 +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 TALER_MasterPrivateKeyP master_priv;
- json_t *wire_xtalerbank;
- json_t *wire_iban;
- const char *payto_xtalerbank = "payto://x-taler-bank/42";
- const char *payto_iban =
- "payto://iban/BIC-TO-BE-SKIPPED/DE89370400440532013000?receiver-name=Test";
- char *p_xtalerbank;
- char *p_iban;
-
- (void) argc;
- (void) argv;
- GNUNET_log_setup ("test-json-wire",
- "WARNING",
- NULL);
- GNUNET_CRYPTO_eddsa_key_create (&master_priv.eddsa_priv);
- GNUNET_CRYPTO_eddsa_key_get_public (&master_priv.eddsa_priv,
- &master_pub.eddsa_pub);
- wire_xtalerbank = TALER_JSON_exchange_wire_signature_make (payto_xtalerbank,
- &master_priv);
- wire_iban = TALER_JSON_exchange_wire_signature_make (payto_iban,
- &master_priv);
- if (NULL == wire_iban)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not parse payto/IBAN (%s) into 'wire object'\n",
- payto_iban);
- return 1;
- }
- p_xtalerbank = TALER_JSON_wire_to_payto (wire_xtalerbank);
- p_iban = TALER_JSON_wire_to_payto (wire_iban);
- GNUNET_assert (0 == strcmp (p_xtalerbank, payto_xtalerbank));
- GNUNET_assert (0 == strcmp (p_iban, payto_iban));
- GNUNET_free (p_xtalerbank);
- GNUNET_free (p_iban);
-
- GNUNET_assert (GNUNET_OK ==
- TALER_JSON_exchange_wire_signature_check (wire_xtalerbank,
- &master_pub));
- GNUNET_assert (GNUNET_OK ==
- TALER_JSON_exchange_wire_signature_check (wire_iban,
- &master_pub));
- json_decref (wire_xtalerbank);
- json_decref (wire_iban);
-
- 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 dd4c527d5..63dab7c80 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -18,50 +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_withdraw2.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 = \
@@ -69,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 \
@@ -80,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 81fcd7bac..972f28ca6 100644
--- a/src/lib/auditor_api_curl_defaults.c
+++ b/src/lib/auditor_api_curl_defaults.c
@@ -19,6 +19,8 @@
* @brief curl easy handle defaults
* @author Florian Dold
*/
+#include "platform.h"
+#include "taler_curl_lib.h"
#include "auditor_api_curl_defaults.h"
@@ -37,22 +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));
+ 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,
""));
- /* 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));
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 cdf1c5ef3..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-2021 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,64 +83,64 @@ handle_deposit_confirmation_finished (void *cls,
{
const json_t *json = djson;
struct TALER_AUDITOR_DepositConfirmationHandle *dh = cls;
- struct TALER_AUDITOR_HttpResponse hr = {
- .reply = json,
- .http_status = (unsigned int) response_code
+ struct TALER_AUDITOR_DepositConfirmationResponse dcr = {
+ .hr.reply = json,
+ .hr.http_status = (unsigned int) response_code
};
dh->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ dcr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
- hr.ec = TALER_EC_NONE;
+ dcr.hr.ec = TALER_EC_NONE;
break;
case MHD_HTTP_BAD_REQUEST:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (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:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (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:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (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:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (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_INTERNAL_SERVER_ERROR:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (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 for auditor deposit confirmation\n",
(unsigned int) response_code,
- hr.ec);
+ dcr.hr.ec);
break;
}
dh->cb (dh->cb_cls,
- &hr);
+ &dcr);
TALER_AUDITOR_deposit_confirmation_cancel (dh);
}
@@ -153,12 +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_extensions hash over the extensions, if any
+ * @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 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
@@ -170,35 +168,40 @@ handle_deposit_confirmation_finished (void *cls,
* @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not
*/
static enum GNUNET_GenericReturnValue
-verify_signatures (const struct TALER_MerchantWireHash *h_wire,
- const struct TALER_ExtensionContractHash *h_extensions,
- const struct TALER_PrivateContractHash *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,
- 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_Timestamp ep_start,
- struct GNUNET_TIME_Timestamp ep_expire,
- struct GNUNET_TIME_Timestamp ep_end,
- const struct TALER_MasterSignatureP *master_sig)
+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)
{
if (GNUNET_OK !=
- TALER_exchange_deposit_confirm_verify (h_contract_terms,
- h_wire,
- h_extensions,
- exchange_timestamp,
- wire_deadline,
- refund_deadline,
- amount_without_fee,
- coin_pub,
- merchant_pub,
- exchange_pub,
- exchange_sig))
+ 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 (
@@ -235,15 +238,20 @@ verify_signatures (const struct TALER_MerchantWireHash *h_wire,
struct TALER_AUDITOR_DepositConfirmationHandle *
TALER_AUDITOR_deposit_confirmation (
- struct TALER_AUDITOR_Handle *auditor,
- const struct TALER_MerchantWireHash *h_wire,
- const struct TALER_ExtensionContractHash *h_extensions,
- const struct TALER_PrivateContractHash *h_contract_terms,
+ 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 *amount_without_fee,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ 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,
@@ -256,21 +264,26 @@ TALER_AUDITOR_deposit_confirmation (
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;
- 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_extensions,
+ h_policy,
h_contract_terms,
exchange_timestamp,
wire_deadline,
refund_deadline,
- amount_without_fee,
- coin_pub,
+ total_without_fee,
+ num_coins,
+ coin_sigs,
merchant_pub,
exchange_pub,
exchange_sig,
@@ -283,25 +296,42 @@ TALER_AUDITOR_deposit_confirmation (
GNUNET_break_op (0);
return NULL;
}
-
+ 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_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_extensions",
- h_extensions),
+ 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_timestamp ("refund_deadline",
- refund_deadline),
+ 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 ("amount_without_fee",
- amount_without_fee),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- coin_pub),
+ 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",
@@ -319,18 +349,17 @@ TALER_AUDITOR_deposit_confirmation (
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,
@@ -353,12 +382,21 @@ 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;
}
diff --git a/src/lib/auditor_api_exchanges.c b/src/lib/auditor_api_exchanges.c
deleted file mode 100644
index 136212258..000000000
--- a/src/lib/auditor_api_exchanges.c
+++ /dev/null
@@ -1,270 +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 /exchanges 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;
- struct TALER_AUDITOR_HttpResponse hr = {
- .reply = json,
- .http_status = (unsigned int) response_code
- };
-
- leh->job = NULL;
- switch (response_code)
- {
- case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- ja = json_object_get (json,
- "exchanges");
- if ( (NULL == ja) ||
- (! json_is_array (ja)) )
- {
- GNUNET_break (0);
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 0;
- break;
- }
-
- ja_len = json_array_size (ja);
- if (ja_len > MAX_EXCHANGES)
- {
- GNUNET_break (0);
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 0;
- break;
- }
- {
- struct TALER_AUDITOR_ExchangeInfo ei[ja_len];
- bool ok;
-
- ok = true;
- 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 = false;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 0;
- break;
- }
- }
- if (! ok)
- break;
- leh->cb (leh->cb_cls,
- &hr,
- ja_len,
- ei);
- 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 */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- default:
- /* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for auditor list-exchanges request\n",
- (unsigned int) response_code,
- (int) hr.ec);
- GNUNET_break_op (0);
- break;
- }
- if (NULL != leh->cb)
- leh->cb (leh->cb_cls,
- &hr,
- 0,
- NULL);
- 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");
- if (NULL == leh->url)
- {
- GNUNET_free (leh);
- return NULL;
- }
- 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,
- &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 1d5522141..000000000
--- a/src/lib/auditor_api_handle.c
+++ /dev/null
@@ -1,547 +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/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.
- */
- char *version;
-
- /**
- * Version information for the callback.
- */
- 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);
-}
-
-
-/**
- * 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] auditor where to store the results we decoded
- * @param[out] vc where to store version compatibility data
- * @return #TALER_EC_NONE on success
- */
-static enum TALER_ErrorCode
-decode_version_json (const json_t *resp_obj,
- struct TALER_AUDITOR_Handle *auditor,
- enum TALER_AUDITOR_VersionCompatibility *vc)
-{
- struct TALER_AUDITOR_VersionInformation *vi = &auditor->vi;
- 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 TALER_EC_GENERIC_JSON_INVALID;
- }
- /* check the version */
- if (GNUNET_OK !=
- GNUNET_JSON_parse (resp_obj,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return TALER_EC_GENERIC_JSON_INVALID;
- }
- if (3 != sscanf (ver,
- "%u:%u:%u",
- &current,
- &revision,
- &age))
- {
- GNUNET_break_op (0);
- return TALER_EC_GENERIC_VERSION_MALFORMED;
- }
- auditor->version = GNUNET_strdup (ver);
- vi->version = auditor->version;
- *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 TALER_EC_NONE;
-}
-
-
-/**
- * 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;
- struct TALER_AUDITOR_HttpResponse hr = {
- .reply = resp_obj,
- .http_status = (unsigned int) response_code
- };
-
- 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:
- /* NOTE: this design is debatable. We MAY want to throw this error at the
- client. We may then still additionally internally re-try. */
- 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");
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- hr.ec = decode_version_json (resp_obj,
- auditor,
- &vc);
- if (TALER_EC_NONE != hr.ec)
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- break;
- }
- auditor->retry_delay = GNUNET_TIME_UNIT_ZERO; /* restart quickly */
- break;
- default:
- hr.ec = TALER_JSON_get_error_code (resp_obj);
- 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) hr.ec);
- 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;
- /* notify application that we failed */
- auditor->version_cb (auditor->version_cb_cls,
- &hr,
- 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,
- &hr,
- &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)
-{
- GNUNET_assert ('/' == path[0]);
- return TALER_url_join (h->url,
- path + 1,
- NULL);
-}
-
-
-/* ********************* 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");
- if (NULL == vr->url)
- {
- struct TALER_AUDITOR_HttpResponse hr = {
- .ec = TALER_EC_GENERIC_CONFIGURATION_INVALID
- };
-
- auditor->version_cb (auditor->version_cb_cls,
- &hr,
- NULL,
- TALER_AUDITOR_VC_PROTOCOL_ERROR);
- return;
- }
- 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,
- &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;
- }
- GNUNET_free (auditor->version);
- 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
index 27eb43bcf..89de0d7f1 100644
--- a/src/lib/exchange_api_auditor_add_denomination.c
+++ b/src/lib/exchange_api_auditor_add_denomination.c
@@ -79,9 +79,9 @@ handle_auditor_add_denomination_finished (void *cls,
{
struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_AuditorAddDenominationResponse adr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
ah->job = NULL;
@@ -90,37 +90,37 @@ handle_auditor_add_denomination_finished (void *cls,
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_GONE:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_PRECONDITION_FAILED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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)
{
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (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) hr.ec,
+ (int) adr.hr.ec,
ah->url);
}
else
{
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = NULL;
+ 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,
@@ -131,7 +131,7 @@ handle_auditor_add_denomination_finished (void *cls,
if (NULL != ah->cb)
{
ah->cb (ah->cb_cls,
- &hr);
+ &adr);
ah->cb = NULL;
}
TALER_EXCHANGE_add_auditor_denomination_cancel (ah);
@@ -142,7 +142,7 @@ struct TALER_EXCHANGE_AuditorAddDenominationHandle *
TALER_EXCHANGE_add_auditor_denomination (
struct GNUNET_CURL_Context *ctx,
const char *url,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_AuditorPublicKeyP *auditor_pub,
const struct TALER_AuditorSignatureP *auditor_sig,
TALER_EXCHANGE_AuditorAddDenominationCallback cb,
@@ -192,16 +192,17 @@ TALER_EXCHANGE_add_auditor_denomination (
GNUNET_JSON_pack_data_auto ("auditor_sig",
auditor_sig));
eh = TALER_AUDITOR_curl_easy_get_ (ah->url);
- GNUNET_assert (NULL != eh);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&ah->post_ctx,
- eh,
- body))
+ 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);
- GNUNET_free (eh);
return NULL;
}
json_decref (body);
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 399eb280a..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-2021 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,363 +22,252 @@
#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"
-enum GNUNET_GenericReturnValue
-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_set_zero (currency,
- &total_in));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_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_any ("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;
- 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),
- 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 (0 >
- 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.timestamp = timestamp;
- /* end type==DEPOSIT */
- }
- else if (0 == strcasecmp (type,
- "WITHDRAW"))
- {
- struct TALER_ReserveSignatureP sig;
- struct TALER_WithdrawRequestPS withdraw_purpose;
- 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",
- &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,
- &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;
-
- key_state = TALER_EXCHANGE_get_keys (exchange);
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
- &withdraw_purpose.
- h_denomination_pub);
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&withdraw_fee,
- &dki->fee_withdraw)) ||
- (0 !=
- TALER_amount_cmp (&withdraw_fee,
- &dki->fee_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
- 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 !=
+ 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;
+}
- if (0 >
- 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_Timestamp 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_timestamp_nbo ("timestamp",
- &pc.timestamp),
- GNUNET_JSON_spec_end ()
- };
- 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_timestamp_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,
- &rh->details.recoup_details.exchange_sig.eddsa_signature,
- &rh->details.recoup_details.exchange_pub.eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 >
- 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_Timestamp 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_any_nbo ("closing_fee",
- &rcc.closing_fee),
- GNUNET_JSON_spec_timestamp_nbo ("timestamp",
- &rcc.timestamp),
- GNUNET_JSON_spec_end ()
- };
+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;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (proof,
+ spec,
+ NULL, NULL))
+ {
+ 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;
+ }
+ return GNUNET_OK;
+}
- 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);
- TALER_payto_hash (rh->details.close_details.receiver_account_details,
- &rcc.h_payto);
- 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_timestamp_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,
- &rh->details.close_details.exchange_sig.eddsa_signature,
- &rh->details.close_details.exchange_pub.eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- 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;
- }
- /* end type==CLOSING */
- }
- else
- {
- /* unexpected 'type', protocol incompatibility, complain! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
+
+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)
+{
+ 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))
+ {
+ 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;
}
+ return GNUNET_OK;
+}
+
- /* check balance = total_in - total_out < withdraw-amount */
- if (0 >
- TALER_amount_subtract (balance,
- &total_in,
- &total_out))
+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)
+{
+ 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 (GNUNET_OK !=
+ GNUNET_JSON_parse (proof,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_econtract_upload_verify2 (
+ &h_econtract,
+ &contract_pub,
+ purse_pub,
+ &contract_sig))
{
- /* total_in < total_out, why did the exchange ever allow this!? */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 ==
+ GNUNET_memcmp (&contract_sig,
+ ccontract_sig))
+ {
+ /* Must be the SAME data, not a conflict! */
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
@@ -386,549 +275,378 @@ TALER_EXCHANGE_parse_reserve_history (
}
-void
-TALER_EXCHANGE_free_reserve_history (
- struct TALER_EXCHANGE_ReserveHistory *rhistory,
- unsigned int len)
+// FIXME: should be used...
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_denomination_conflict_ (
+ const json_t *proof,
+ const struct TALER_DenominationHashP *ch_denom_pub)
{
- for (unsigned int i = 0; i<len; i++)
+ 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))
{
- 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_RECOUP:
- break;
- case TALER_EXCHANGE_RTT_CLOSE:
- break;
- }
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 ==
+ GNUNET_memcmp (ch_denom_pub,
+ &h_denom_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_OK;
}
- GNUNET_free (rhistory);
+ /* indeed, proof with different denomination key provided */
+ return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
-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_DenominationHash *h_denom_pub,
- struct TALER_Amount *total)
+TALER_EXCHANGE_get_min_denomination_ (
+ const struct TALER_EXCHANGE_Keys *keys,
+ struct TALER_Amount *min)
{
- size_t len;
- struct TALER_Amount rtotal;
- struct TALER_Amount fee;
+ 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 (NULL == history)
+ if (! have_min)
+ {
+ *min = dk->value;
+ have_min = true;
+ continue;
+ }
+ if (1 != TALER_amount_cmp (min,
+ &dk->value))
+ continue;
+ *min = dk->value;
+ }
+ if (! have_min)
{
- GNUNET_break_op (0);
+ GNUNET_break (0);
return GNUNET_SYSERR;
}
- len = json_array_size (history);
- if (0 == len)
+ 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;
}
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (currency,
- total));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (currency,
- &rtotal));
- for (size_t off = 0; off<len; off++)
+
+ /* check coin signature */
{
- int add;
- json_t *transaction;
- struct TALER_Amount amount;
- const char *type;
- struct GNUNET_JSON_Specification spec_glob[] = {
- TALER_JSON_spec_amount_any ("amount",
- &amount),
- GNUNET_JSON_spec_string ("type",
- &type),
- GNUNET_JSON_spec_end ()
+ 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,
};
- transaction = json_array_get (history,
- off);
- 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 (&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_MerchantWireHash h_wire;
- struct TALER_PrivateContractHash h_contract_terms;
- // struct TALER_ExtensionContractHash h_extensions; // FIXME!
- struct GNUNET_TIME_Timestamp wallet_timestamp;
- struct TALER_MerchantPublicKeyP merchant_pub;
- struct GNUNET_TIME_Timestamp refund_deadline = {0};
- 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",
- &h_contract_terms),
- GNUNET_JSON_spec_fixed_auto ("h_wire",
- &h_wire),
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- h_denom_pub),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &wallet_timestamp),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &refund_deadline)),
- TALER_JSON_spec_amount_any ("deposit_fee",
- &fee),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &merchant_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_wallet_deposit_verify (&amount,
- &fee,
- &h_wire,
- &h_contract_terms,
- NULL /* h_extensions! */,
- h_denom_pub,
- wallet_timestamp,
- &merchant_pub,
- refund_deadline,
- coin_pub,
- &sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (NULL != dk)
- {
- /* check that deposit fee matches our expectations from /keys! */
- 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_CoinSpendSignatureP sig;
- struct TALER_RefreshCommitmentP rc;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &sig),
- GNUNET_JSON_spec_fixed_auto ("rc",
- &rc),
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- h_denom_pub),
- TALER_JSON_spec_amount_any ("melt_fee",
- &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;
- }
- if (NULL != dk)
- {
- /* check that melt fee matches our expectations from /keys! */
- 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;
- }
- }
- if (GNUNET_OK !=
- TALER_wallet_melt_verify (&amount,
- &fee,
- &rc,
- h_denom_pub,
- coin_pub,
- &sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- add = GNUNET_YES;
- }
- else if (0 == strcasecmp (type,
- "REFUND"))
- {
- struct TALER_PrivateContractHash 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;
- struct GNUNET_JSON_Specification spec[] = {
- 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 (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 >
- TALER_amount_add (&sig_amount,
- &refund_fee,
- &amount))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_merchant_refund_verify (coin_pub,
- &h_contract_terms,
- rtransaction_id,
- &sig_amount,
- &merchant_pub,
- &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 (NULL != dk)
- {
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&refund_fee,
- &dk->fee_refund)) ||
- (0 !=
- TALER_amount_cmp (&refund_fee,
- &dk->fee_refund)) )
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- add = GNUNET_NO;
- }
- else if (0 == strcasecmp (type,
- "RECOUP"))
- {
- struct TALER_RecoupConfirmationPS pc = {
- .purpose.size = htonl (sizeof (pc)),
- .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP),
- .coin_pub = *coin_pub
- };
- union TALER_DenominationBlindingKeyP coin_bks;
- struct TALER_Amount recoup_amount;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangeSignatureP exchange_sig;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any_nbo ("amount",
- &pc.recoup_amount),
- TALER_JSON_spec_amount_any ("amount",
- &recoup_amount),
- 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_fixed_auto ("coin_sig",
- &coin_sig),
- GNUNET_JSON_spec_fixed_auto ("coin_blind",
- &coin_bks),
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- h_denom_pub),
- GNUNET_JSON_spec_timestamp_nbo ("timestamp",
- &pc.timestamp),
- GNUNET_JSON_spec_end ()
- };
+/**
+ * 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 (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- TALER_amount_hton (&pc.recoup_amount,
- &amount);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP,
- &pc,
- &exchange_sig.eddsa_signature,
- &exchange_pub.eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_recoup_verify (h_denom_pub,
- &coin_bks,
- coin_pub,
- &coin_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- add = GNUNET_YES;
+ 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,
- "RECOUP-REFRESH"))
+ if (0 == strcmp (type,
+ "deny"))
{
- /* This is the coin that was subjected to a recoup,
- the value being credited to the old coin. */
- struct TALER_RecoupRefreshConfirmationPS pc = {
- .purpose.size = htonl (sizeof (pc)),
- .purpose.purpose = htonl (
- TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH),
- .coin_pub = *coin_pub
- };
- union TALER_DenominationBlindingKeyP coin_bks;
- struct TALER_Amount recoup_amount;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangeSignatureP exchange_sig;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any_nbo ("amount",
- &pc.recoup_amount),
- TALER_JSON_spec_amount_any ("amount",
- &recoup_amount),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &exchange_pub),
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &coin_sig),
- GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
- &pc.old_coin_pub),
- GNUNET_JSON_spec_fixed_auto ("coin_blind",
- &coin_bks),
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- h_denom_pub),
- GNUNET_JSON_spec_timestamp_nbo ("timestamp",
- &pc.timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- TALER_amount_hton (&pc.recoup_amount,
- &amount);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (
- TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH,
- &pc,
- &exchange_sig.eddsa_signature,
- &exchange_pub.eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_recoup_verify (h_denom_pub,
- &coin_bks,
- coin_pub,
- &coin_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- add = GNUNET_YES;
+ ar->type = TALER_EXCHANGE_AR_DENY;
+ continue;
}
- else if (0 == strcasecmp (type,
- "OLD-COIN-RECOUP"))
+ if (0 == strcmp (type,
+ "regex"))
{
- /* This is the coin that was credited in a recoup,
- the value being credited to the this coin. */
- struct TALER_RecoupRefreshConfirmationPS pc = {
- .purpose.size = htonl (sizeof (pc)),
- .purpose.purpose = htonl (
- TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH),
- .old_coin_pub = *coin_pub
- };
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangeSignatureP exchange_sig;
+ const char *regex;
+ const char *hint;
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any_nbo ("amount",
- &pc.recoup_amount),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &exchange_pub),
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &pc.coin_pub),
- GNUNET_JSON_spec_timestamp_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;
}
- TALER_amount_hton (&pc.recoup_amount,
- &amount);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (
- TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH,
- &pc,
- &exchange_sig.eddsa_signature,
- &exchange_pub.eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- add = GNUNET_NO;
+ 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_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected type `%s' in response\n",
- type);
- 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 (0 >
- 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 (0 >
- 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;
}
- }
+ 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;
+}
- /* Finally, subtract 'rtotal' from total to handle the subtractions */
- if (0 >
- TALER_amount_subtract (total,
- total,
- &rtotal))
+/**
+ * Free array of account restrictions.
+ *
+ * @param ar_len length of @a ar
+ * @param[in] ar array to free contents of (but not @a ar itself)
+ */
+static void
+free_restrictions (unsigned int ar_len,
+ struct TALER_EXCHANGE_AccountRestriction ar[static ar_len])
+{
+ for (unsigned int i = 0; i<ar_len; i++)
{
- /* underflow in history? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
+ 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;
+ }
}
-
- return GNUNET_OK;
}
-const struct TALER_EXCHANGE_SigningPublicKey *
-TALER_EXCHANGE_get_signing_key_info (
- const struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_ExchangePublicKeyP *exchange_pub)
+void
+TALER_EXCHANGE_free_accounts (
+ unsigned int was_len,
+ struct TALER_EXCHANGE_WireAccount was[static was_len])
{
- for (unsigned int i = 0; i<keys->num_sign_keys; i++)
+ for (unsigned int i = 0; i<was_len; i++)
{
- const struct TALER_EXCHANGE_SigningPublicKey *spk
- = &keys->sign_keys[i];
-
- if (0 == GNUNET_memcmp (exchange_pub,
- &spk->key))
- return spk;
+ 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 9627db9ff..85a32189b 100644
--- a/src/lib/exchange_api_curl_defaults.c
+++ b/src/lib/exchange_api_curl_defaults.c
@@ -19,7 +19,8 @@
* @brief curl easy handle defaults
* @author Florian Dold
*/
-
+#include "platform.h"
+#include "taler_curl_lib.h"
#include "exchange_api_curl_defaults.h"
@@ -38,22 +39,14 @@ TALER_EXCHANGE_curl_easy_get_ (const char *url)
curl_easy_setopt (eh,
CURLOPT_URL,
url));
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_FOLLOWLOCATION,
- 1L));
+ 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,
""));
- /* 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));
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 de67bc5f2..000000000
--- a/src/lib/exchange_api_deposit.c
+++ /dev/null
@@ -1,753 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
- */
-/**
- * @file 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;
-
- /**
- * Hash over the contract for which this deposit is made.
- */
- struct TALER_PrivateContractHash h_contract_terms GNUNET_PACKED;
-
- /**
- * Hash over the wiring information of the merchant.
- */
- struct TALER_MerchantWireHash h_wire GNUNET_PACKED;
-
- /**
- * Hash over the extension options of the deposit, 0 if there
- * were not extension options.
- */
- struct TALER_ExtensionContractHash h_extensions GNUNET_PACKED;
-
- /**
- * Time when this confirmation was generated / when the exchange received
- * the deposit request.
- */
- struct GNUNET_TIME_Timestamp exchange_timestamp;
-
- /**
- * By when does the exchange expect to pay the merchant
- * (as per the merchant's request).
- */
- struct GNUNET_TIME_Timestamp 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_Timestamp refund_deadline;
-
- /**
- * Amount to be deposited, excluding fee. Calculated from the
- * amount with fee and the fee from the deposit request.
- */
- struct TALER_Amount amount_without_fee;
-
- /**
- * The public key of the coin that was deposited.
- */
- 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_pub;
-
- /**
- * 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 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;
- }
- aie = GNUNET_new (struct TEAH_AuditorInteractionEntry);
- aie->dch = TALER_AUDITOR_deposit_confirmation (
- ah,
- &dh->h_wire,
- &dh->h_extensions,
- &dh->h_contract_terms,
- dh->exchange_timestamp,
- dh->wire_deadline,
- dh->refund_deadline,
- &dh->amount_without_fee,
- &dh->coin_pub,
- &dh->merchant_pub,
- &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 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 enum GNUNET_GenericReturnValue
-verify_deposit_signature_conflict (
- const struct TALER_EXCHANGE_DepositHandle *dh,
- const json_t *json)
-{
- json_t *history;
- struct TALER_Amount total;
- enum TALER_ErrorCode ec;
- struct TALER_DenominationHash h_denom_pub;
-
- memset (&h_denom_pub,
- 0,
- sizeof (h_denom_pub));
- history = json_object_get (json,
- "history");
- if (GNUNET_OK !=
- TALER_EXCHANGE_verify_coin_history (&dh->dki,
- dh->dki.value.currency,
- &dh->coin_pub,
- history,
- &h_denom_pub,
- &total))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- ec = TALER_JSON_get_error_code (json);
- switch (ec)
- {
- case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
- if (0 >
- 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;
- case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
- if (0 != GNUNET_memcmp (&dh->dki.h_key,
- &h_denom_pub))
- return GNUNET_OK; /* indeed, proof with different denomination key provided */
- /* invalid proof provided */
- return GNUNET_SYSERR;
- default:
- /* unexpected error code */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-}
-
-
-/**
- * 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;
- const json_t *j = response;
- struct TALER_EXCHANGE_DepositResult dr = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- dh->job = NULL;
- switch (response_code)
- {
- case 0:
- dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- const struct TALER_EXCHANGE_Keys *key_state;
- 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 (
- GNUNET_JSON_spec_string ("transaction_base_url",
- &dr.details.success.transaction_base_url)),
- 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;
- }
- key_state = TALER_EXCHANGE_get_keys (dh->exchange);
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- &dh->exchange_pub))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
- break;
- }
-
- if (GNUNET_OK !=
- TALER_exchange_deposit_confirm_verify (&dh->h_contract_terms,
- &dh->h_wire,
- &dh->h_extensions,
- dh->exchange_timestamp,
- dh->wire_deadline,
- dh->refund_deadline,
- &dh->amount_without_fee,
- &dh->coin_pub,
- &dh->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->exchange,
- &auditor_cb,
- dh);
-
- }
- dr.details.success.exchange_sig = &dh->exchange_sig;
- dr.details.success.exchange_pub = &dh->exchange_pub;
- dr.details.success.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:
- /* Double spending; check signatures on transaction history */
- if (GNUNET_OK !=
- verify_deposit_signature_conflict (dh,
- j))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
- }
- else
- {
- 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;
- }
- dh->cb (dh->cb_cls,
- &dr);
- 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_sig exchange’s unblinded signature of the coin
- * @param denom_pub denomination key with which the coin is signed
- * @param denom_pub_hash hash of @a denom_pub
- * @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 enum GNUNET_GenericReturnValue
-verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki,
- const struct TALER_Amount *amount,
- const struct TALER_MerchantWireHash *h_wire,
- const struct TALER_PrivateContractHash *h_contract_terms,
- const struct TALER_ExtensionContractHash *ech,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_DenominationHash *denom_pub_hash,
- struct GNUNET_TIME_Timestamp timestamp,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- struct GNUNET_TIME_Timestamp refund_deadline,
- const struct TALER_CoinSpendSignatureP *coin_sig)
-{
- if (GNUNET_OK !=
- TALER_wallet_deposit_verify (amount,
- &dki->fee_deposit,
- h_wire,
- h_contract_terms,
- ech,
- denom_pub_hash,
- timestamp,
- merchant_pub,
- refund_deadline,
- coin_pub,
- 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 (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,
- .age_commitment_hash = {{{0}}} /* FIXME-Oec */
- };
-
- 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;
-}
-
-
-struct TALER_EXCHANGE_DepositHandle *
-TALER_EXCHANGE_deposit (
- struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_Amount *amount,
- struct GNUNET_TIME_Timestamp wire_deadline,
- const char *merchant_payto_uri,
- const struct TALER_WireSalt *wire_salt,
- const struct TALER_PrivateContractHash *h_contract_terms,
- const json_t *extension_details,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_DenominationPublicKey *denom_pub,
- struct GNUNET_TIME_Timestamp timestamp,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- struct GNUNET_TIME_Timestamp refund_deadline,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- TALER_EXCHANGE_DepositResultCallback cb,
- void *cb_cls,
- enum TALER_ErrorCode *ec)
-{
- 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 TALER_MerchantWireHash h_wire;
- struct TALER_DenominationHash denom_pub_hash;
- struct TALER_Amount amount_without_fee;
- struct TALER_ExtensionContractHash ech;
- char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
-
- if (NULL != extension_details)
- TALER_deposit_extension_hash (extension_details,
- &ech);
- {
- 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);
- }
- if (GNUNET_TIME_timestamp_cmp (refund_deadline,
- >,
- wire_deadline))
- {
- GNUNET_break_op (0);
- *ec = TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE;
- return NULL;
- }
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
- /* initialize h_wire */
- TALER_merchant_wire_signature_hash (merchant_payto_uri,
- wire_salt,
- &h_wire);
- key_state = TALER_EXCHANGE_get_keys (exchange);
- dki = TALER_EXCHANGE_get_denomination_key (key_state,
- denom_pub);
- if (NULL == dki)
- {
- *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
- GNUNET_break_op (0);
- return NULL;
- }
- if (0 >
- TALER_amount_subtract (&amount_without_fee,
- amount,
- &dki->fee_deposit))
- {
- *ec = TALER_EC_EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT;
- GNUNET_break_op (0);
- return NULL;
- }
- TALER_denom_pub_hash (denom_pub,
- &denom_pub_hash);
- if (GNUNET_OK !=
- verify_signatures (dki,
- amount,
- &h_wire,
- h_contract_terms,
- (NULL != extension_details)
- ? &ech
- : NULL,
- coin_pub,
- denom_sig,
- denom_pub,
- &denom_pub_hash,
- timestamp,
- merchant_pub,
- refund_deadline,
- coin_sig))
- {
- *ec = TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID;
- GNUNET_break_op (0);
- return NULL;
- }
-
- deposit_obj = GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("contribution",
- amount),
- GNUNET_JSON_pack_string ("merchant_payto_uri",
- merchant_payto_uri),
- GNUNET_JSON_pack_data_auto ("wire_salt",
- wire_salt),
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- h_contract_terms),
- GNUNET_JSON_pack_data_auto ("denom_pub_hash",
- &denom_pub_hash),
- TALER_JSON_pack_denom_sig ("ub_sig",
- denom_sig),
- GNUNET_JSON_pack_timestamp ("timestamp",
- timestamp),
- GNUNET_JSON_pack_data_auto ("merchant_pub",
- merchant_pub),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_timestamp ("refund_deadline",
- refund_deadline)),
- GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
- wire_deadline),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- coin_sig));
- 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);
- if (NULL == dh->url)
- {
- GNUNET_break (0);
- *ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
- GNUNET_free (dh);
- json_decref (deposit_obj);
- return NULL;
- }
- dh->h_contract_terms = *h_contract_terms;
- dh->h_wire = h_wire;
- /* dh->h_extensions = ... */
- dh->refund_deadline = refund_deadline;
- dh->wire_deadline = wire_deadline;
- dh->amount_without_fee = amount_without_fee;
- dh->coin_pub = *coin_pub;
- dh->merchant_pub = *merchant_pub;
- dh->amount_with_fee = *amount;
- dh->dki = *dki;
- memset (&dh->dki.key,
- 0,
- sizeof (dh->dki.key)); /* lifetime not warranted, so better
- not copy the contents! */
-
- eh = TALER_EXCHANGE_curl_easy_get_ (dh->url);
- if ( (NULL == eh) ||
- (GNUNET_OK !=
- TALER_curl_easy_post (&dh->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->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;
-}
-
-
-void
-TALER_EXCHANGE_deposit_force_dc (struct TALER_EXCHANGE_DepositHandle *deposit)
-{
- deposit->auditor_chance = 1;
-}
-
-
-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 18fb9b7bd..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-2021 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_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,50 +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 exchange_pub the exchange's public key
- * @param exchange_sig the exchange's signature
- * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
- */
-static enum GNUNET_GenericReturnValue
-verify_deposit_wtid_signature_ok (
- const struct TALER_EXCHANGE_DepositGetHandle *dwh,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const struct TALER_ExchangeSignatureP *exchange_sig)
-{
- const struct TALER_EXCHANGE_Keys *key_state;
+ /**
+ * The coin's public key. This is the value that must have been
+ * signed (blindly) by the Exchange.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
- 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,
- &exchange_sig->eddsa_signature,
- &exchange_pub->eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
+};
/**
@@ -146,18 +118,21 @@ handle_deposit_wtid_finished (void *cls,
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("wtid",
- &dr.details.success.wtid),
+ &dr.details.ok.wtid),
GNUNET_JSON_spec_timestamp ("execution_time",
- &dr.details.success.execution_time),
+ &dr.details.ok.execution_time),
TALER_JSON_spec_amount_any ("coin_contribution",
- &dr.details.success.coin_contribution),
+ &dr.details.ok.coin_contribution),
GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &dr.details.success.exchange_sig),
+ &dr.details.ok.exchange_sig),
GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &dr.details.success.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,
@@ -168,15 +143,25 @@ handle_deposit_wtid_finished (void *cls,
dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
- dwh->depconf.execution_time = GNUNET_TIME_timestamp_hton (
- dr.details.success.execution_time);
- dwh->depconf.wtid = dr.details.success.wtid;
- TALER_amount_hton (&dwh->depconf.coin_contribution,
- &dr.details.success.coin_contribution);
if (GNUNET_OK !=
- verify_deposit_wtid_signature_ok (dwh,
- &dr.details.success.exchange_pub,
- &dr.details.success.exchange_sig))
+ TALER_EXCHANGE_test_signing_key (key_state,
+ &dr.details.ok.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
+ }
+ 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))
{
GNUNET_break_op (0);
dr.hr.http_status = 0;
@@ -191,11 +176,16 @@ handle_deposit_wtid_finished (void *cls,
case MHD_HTTP_ACCEPTED:
{
/* Transaction known, but not executed yet */
+ bool no_legi = false;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_timestamp ("execution_time",
&dr.details.accepted.execution_time),
- GNUNET_JSON_spec_uint64 ("payment_target_uuid",
- &dr.details.accepted.payment_target_uuid),
+ 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 ()
@@ -211,6 +201,8 @@ handle_deposit_wtid_finished (void *cls,
dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
+ if (no_legi)
+ dr.details.accepted.requirement_row = 0;
dwh->cb (dwh->cb_cls,
&dr);
TALER_EXCHANGE_deposits_get_cancel (dwh);
@@ -258,63 +250,46 @@ handle_deposit_wtid_finished (void *cls,
}
-/**
- * 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 TALER_MerchantWireHash *h_wire,
- const struct TALER_PrivateContractHash *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 TALER_MerchantWireHash)
+ + sizeof (struct TALER_MerchantWireHashP)
+ sizeof (struct TALER_MerchantPublicKeyP)
- + sizeof (struct TALER_PrivateContractHash)
+ + 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_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv,
- &dtp,
- &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 TALER_PrivateContractHash) * 2];
- char whash_str[sizeof (struct TALER_MerchantWireHash) * 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,
@@ -322,8 +297,8 @@ TALER_EXCHANGE_deposits_get (
whash_str,
sizeof (whash_str));
*end = '\0';
- end = GNUNET_STRINGS_data_to_string (&dtp.merchant,
- sizeof (dtp.merchant),
+ end = GNUNET_STRINGS_data_to_string (&merchant,
+ sizeof (merchant),
mpub_str,
sizeof (mpub_str));
*end = '\0';
@@ -342,34 +317,47 @@ TALER_EXCHANGE_deposits_get (
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->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == dwh->url)
{
GNUNET_free (dwh);
return NULL;
}
- 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->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)
{
@@ -378,21 +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,
&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)
{
@@ -403,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 ac0e0584f..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-2021 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
@@ -40,12 +40,17 @@
* Which version of the Taler protocol is implemented
* by this library? Used to determine compatibility.
*/
-#define EXCHANGE_PROTOCOL_CURRENT 12
+#define EXCHANGE_PROTOCOL_CURRENT 19
/**
* How many versions are we backwards compatible with?
*/
-#define EXCHANGE_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
@@ -54,7 +59,7 @@
#define EXCHANGE_SERIALIZATION_FORMAT_VERSION 0
/**
- * How far off do we allow key liftimes to be?
+ * How far off do we allow key lifetimes to be?
*/
#define LIFETIME_TOLERANCE GNUNET_TIME_UNIT_HOURS
@@ -65,175 +70,311 @@
#define DEFAULT_EXPIRATION GNUNET_TIME_UNIT_HOURS
/**
- * Set to 1 for extra debug logging.
- */
-#define DEBUG 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));
-
-
-/**
- * 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.
+ * The exchange base URL (i.e. "https://exchange.demo.taler.net/")
*/
- struct TEAH_AuditorListEntry *prev;
+ char *exchange_url;
/**
- * Base URL of the auditor.
+ * The url for the /keys request.
*/
- char *auditor_url;
+ char *url;
/**
- * Handle to the auditor.
+ * Previous /keys response, NULL for none.
*/
- struct TALER_AUDITOR_Handle *ah;
+ struct TALER_EXCHANGE_Keys *prev_keys;
/**
- * Head of DLL of interactions with this auditor.
+ * Entry for this request with the `struct GNUNET_CURL_Context`.
*/
- struct TEAH_AuditorInteractionEntry *ai_head;
+ struct GNUNET_CURL_Job *job;
/**
- * Tail of DLL of interactions with this auditor.
+ * Expiration time according to "Expire:" header.
+ * 0 if not provided by the server.
*/
- struct TEAH_AuditorInteractionEntry *ai_tail;
+ struct GNUNET_TIME_Timestamp expire;
/**
- * Public key of the auditor.
+ * Function to call with the exchange's certification data,
+ * NULL if this has already been done.
*/
- struct TALER_AuditorPublicKeyP auditor_pub;
+ TALER_EXCHANGE_GetKeysCallback cert_cb;
/**
- * Flag indicating that the auditor is available and that protocol
- * version compatibility is given.
+ * Closure to pass to @e cert_cb.
*/
- int is_up;
+ void *cert_cb_cls;
};
-/* ***************** Internal /keys fetching ************* */
-
/**
- * Data for the request to get the /keys of a exchange.
+ * Element in the `struct SignatureContext` array.
*/
-struct KeysRequest
+struct SignatureElement
{
+
/**
- * The connection to exchange this request handle will use
+ * Offset of the denomination in the group array,
+ * for sorting (2nd rank, ascending).
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ unsigned int offset;
/**
- * The url for this handle
+ * Offset of the group in the denominations array,
+ * for sorting (2nd rank, ascending).
*/
- char *url;
+ unsigned int group_offset;
/**
- * Entry for this request with the `struct GNUNET_CURL_Context`.
+ * Pointer to actual master signature to hash over.
*/
- struct GNUNET_CURL_Job *job;
+ struct TALER_MasterSignatureP master_sig;
+};
+/**
+ * Context for collecting the array of master signatures
+ * needed to verify the exchange_sig online signature.
+ */
+struct SignatureContext
+{
/**
- * Expiration time according to "Expire:" header.
- * 0 if not provided by the server.
+ * Array of signatures to hash over.
*/
- struct GNUNET_TIME_Timestamp expire;
+ struct SignatureElement *elements;
+
+ /**
+ * Write offset in the @e elements array.
+ */
+ unsigned int elements_pos;
+ /**
+ * Allocated space for @e elements.
+ */
+ unsigned int elements_size;
};
-void
-TEAH_acc_confirmation_cb (void *cls,
- const struct TALER_AUDITOR_HttpResponse *hr)
+/**
+ * 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)
{
- struct TEAH_AuditorInteractionEntry *aie = cls;
- struct TEAH_AuditorListEntry *ale = aie->ale;
+ const struct SignatureElement *sa = a;
+ const struct SignatureElement *sb = b;
- if (MHD_HTTP_OK != 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",
- ale->auditor_url,
- hr->http_status,
- hr->ec);
- }
- GNUNET_CONTAINER_DLL_remove (ale->ai_head,
- ale->ai_tail,
- aie);
- GNUNET_free (aie);
+ 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;
}
-void
-TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h,
- TEAH_AuditorCallback ac,
- void *ac_cls)
+/**
+ * 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)
{
- if (NULL == h->auditors_head)
+ struct SignatureElement *element;
+ unsigned int new_size;
+
+ if (sig_ctx->elements_pos == sig_ctx->elements_size)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No auditor available for exchange `%s'. Not submitting deposit confirmations.\n",
- h->url);
- return;
+ 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);
}
- for (struct TEAH_AuditorListEntry *ale = h->auditors_head;
- NULL != ale;
- ale = ale->next)
+ element = &sig_ctx->elements[sig_ctx->elements_pos++];
+ element->offset = offset;
+ element->group_offset = group_offset;
+ element->master_sig = *master_sig;
+}
+
+
+/**
+ * Frees @a wfm array.
+ *
+ * @param wfm fee array to release
+ * @param wfm_len length of the @a wfm array
+ */
+static void
+free_fees (struct TALER_EXCHANGE_WireFeesByMethod *wfm,
+ unsigned int wfm_len)
+{
+ for (unsigned int i = 0; i<wfm_len; i++)
{
- 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)
{
- GNUNET_free (kr->url);
- GNUNET_free (kr);
+ 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)
+{
+ 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);
+ }
}
@@ -248,21 +389,20 @@ 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 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_timestamp ("stamp_start",
@@ -282,7 +422,6 @@ 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 !=
@@ -292,33 +431,45 @@ parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key,
sign_key->valid_until,
sign_key->valid_legal,
master_key,
- &sign_key_issue_sig))
+ &sign_key->master_sig))
{
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 enum GNUNET_GenericReturnValue
-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)
+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",
@@ -331,22 +482,16 @@ parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key,
&denom_key->valid_from),
GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
&denom_key->expire_legal),
- TALER_JSON_spec_amount_any ("value",
- &denom_key->value),
- 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),
- TALER_JSON_spec_denom_pub ("denom_pub",
- &denom_key->key),
+ 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 ()
};
-
if (GNUNET_OK !=
GNUNET_JSON_parse (denom_key_obj,
spec,
@@ -355,13 +500,13 @@ parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
-
TALER_denom_pub_hash (&denom_key->key,
&denom_key->h_key);
- if (NULL != hash_context)
- GNUNET_CRYPTO_hash_context_read (hash_context,
- &denom_key->h_key,
- sizeof (struct GNUNET_HashCode));
+ if (NULL != sig_ctx)
+ append_signature (sig_ctx,
+ group_offset,
+ index,
+ &denom_key->master_sig);
if (! check_sigs)
return GNUNET_OK;
EXITIF (GNUNET_SYSERR ==
@@ -372,19 +517,16 @@ parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key,
denom_key->expire_deposit,
denom_key->expire_legal,
&denom_key->value,
- &denom_key->fee_withdraw,
- &denom_key->fee_deposit,
- &denom_key->fee_refresh,
- &denom_key->fee_refund,
+ &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;
}
@@ -394,30 +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 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 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 ()
};
@@ -435,16 +576,15 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
return GNUNET_SYSERR;
}
auditor->auditor_url = GNUNET_strdup (auditor_url);
- 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 TALER_DenominationHash 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),
@@ -461,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,
@@ -492,126 +630,95 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
dk->expire_deposit,
dk->expire_legal,
&dk->value,
- &dk->fee_withdraw,
- &dk->fee_deposit,
- &dk->fee_refresh,
- &dk->fee_refund,
+ &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 hr http response from the auditor
- * @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_HttpResponse *hr,
- 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;
+ 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 ()
+ };
- (void) hr;
- if (NULL == vi)
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (fee_obj,
+ spec,
+ NULL, NULL))
{
- /* 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;
+ GNUNET_break_op (0);
+#if DEBUG
+ json_dumpf (fee_obj,
+ stderr,
+ JSON_INDENT (2));
+#endif
+ return GNUNET_SYSERR;
}
-
- if (0 != (TALER_AUDITOR_VC_INCOMPATIBLE & compat))
+ if (check_sigs)
{
- 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;
- }
- 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++)
- {
- /* 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;
}
@@ -621,7 +728,7 @@ 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 (const struct TALER_EXCHANGE_DenomPublicKey *denom1,
@@ -666,28 +773,21 @@ decode_keys_json (const json_t *resp_obj,
struct TALER_EXCHANGE_Keys *key_data,
enum TALER_EXCHANGE_VersionCompatibility *vc)
{
- struct TALER_ExchangeSignatureP sig;
- struct GNUNET_HashContext *hash_context;
- struct TALER_ExchangePublicKeyP pub;
- const char *currency;
- struct GNUNET_JSON_Specification mspec[] = {
- GNUNET_JSON_spec_fixed_auto ("eddsa_sig",
- &sig),
- GNUNET_JSON_spec_fixed_auto ("eddsa_pub",
- &pub),
- GNUNET_JSON_spec_fixed_auto ("master_public_key",
- &key_data->master_pub),
- 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_mark_optional (
- TALER_JSON_spec_amount_any ("wallet_balance_limit_without_kyc",
- &key_data->wallet_balance_limit_without_kyc)),
- 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))
{
@@ -699,16 +799,12 @@ decode_keys_json (const json_t *resp_obj,
stderr,
JSON_INDENT (2));
#endif
- /* check the version */
+ /* check the version first */
{
- const char *ver;
- unsigned int age;
- unsigned int revision;
- unsigned int current;
- char dummy;
+ 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 ()
};
@@ -720,184 +816,331 @@ decode_keys_json (const json_t *resp_obj,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if (3 != sscanf (ver,
- "%u:%u:%u%c",
- &current,
- &revision,
- &age,
- &dummy))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
*vc = TALER_EXCHANGE_VC_MATCH;
- if (EXCHANGE_PROTOCOL_CURRENT < current)
+ if (EXCHANGE_PROTOCOL_CURRENT < pv.current)
{
*vc |= TALER_EXCHANGE_VC_NEWER;
- if (EXCHANGE_PROTOCOL_CURRENT < current - age)
+ if (EXCHANGE_PROTOCOL_CURRENT < pv.current - pv.age)
*vc |= TALER_EXCHANGE_VC_INCOMPATIBLE;
}
- if (EXCHANGE_PROTOCOL_CURRENT > current)
+ if (EXCHANGE_PROTOCOL_CURRENT > pv.current)
{
*vc |= TALER_EXCHANGE_VC_OLDER;
- if (EXCHANGE_PROTOCOL_CURRENT - EXCHANGE_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));
- key_data->currency = GNUNET_strdup (currency);
+ sspec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Parsing /keys failed for `%s' (%u)\n",
+ emsg,
+ eline);
+ EXITIF (1);
+ }
+ }
+
+ 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);
+ }
- if (GNUNET_OK ==
- TALER_amount_is_valid (&key_data->wallet_balance_limit_without_kyc))
+ /* 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)
{
- if (0 != strcasecmp (currency,
- key_data->wallet_balance_limit_without_kyc.currency))
+ 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)
{
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
+ EXITIF (GNUNET_SYSERR ==
+ parse_global_fee (&key_data->global_fees[index],
+ check_sig,
+ global_fee,
+ key_data));
}
}
- /* parse the master public key and issue date of the response */
- if (check_sig)
- hash_context = GNUNET_CRYPTO_hash_context_start ();
-
/* 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_is_array (sign_keys_array));
- if (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);
- json_array_foreach (sign_keys_array, index, sign_key_obj) {
- EXITIF (GNUNET_SYSERR ==
- parse_json_signkey (&key_data->sign_keys[index],
- check_sig,
- sign_key_obj,
- &key_data->master_pub));
- }
+ key_data->sign_keys
+ = GNUNET_new_array (key_data->num_sign_keys,
+ struct TALER_EXCHANGE_SigningPublicKey);
+ json_array_foreach (sign_keys_array, index, sign_key_obj) {
+ EXITIF (GNUNET_SYSERR ==
+ parse_json_signkey (&key_data->sign_keys[index],
+ check_sig,
+ sign_key_obj,
+ &key_data->master_pub));
}
}
- /* Parse the supported extension(s): age-restriction. */
- /* TODO: maybe lift this into a FP in TALER_Extension ? */
+ /* Parse balance limits */
+ if (NULL != wblwk)
{
- json_t *age_restriction = json_object_get (resp_obj,
- "age_restriction");
-
- if (NULL != age_restriction)
+ 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++)
{
- bool critical;
- const char *version;
- const char *age_groups;
+ 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[] = {
- GNUNET_JSON_spec_bool ("critical",
- &critical),
- GNUNET_JSON_spec_string ("version",
- &version),
- GNUNET_JSON_spec_string ("age_groups",
- &age_groups),
+ TALER_JSON_spec_amount (NULL,
+ key_data->currency,
+ a),
GNUNET_JSON_spec_end ()
};
- if (GNUNET_OK !=
- GNUNET_JSON_parse (age_restriction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
+ EXITIF (GNUNET_OK !=
+ GNUNET_JSON_parse (aj,
+ spec,
+ NULL, NULL));
+ }
+ }
- if (critical || // do we care?
- 0 != strncmp (version, "1", 1) ) /* TODO: better compatibility check */
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
+ /* 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));
- if (TALER_Extension_OK !=
- TALER_parse_age_group_string (age_groups,
- &key_data->age_mask))
- {
- // TODO: print more specific error?
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Parsed %u wire accounts from JSON\n",
+ key_data->accounts_len);
+
+
+ /* 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) */
+ /*
+ * 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}.
+ */
{
- /* The denominations can be in "denoms" and (optionally) in
- * "age_restricted_denoms"
- */
- struct
- { char *name;
- bool is_optional_age_restriction;} hive[2] = {
- { "denoms", false },
- { "age_restricted_denoms", true },
- };
+ json_t *group_obj;
+ unsigned int group_idx;
- for (size_t s = 0; s < sizeof(hive) / sizeof(hive[0]); s++)
+ json_array_foreach (denominations_by_group,
+ group_idx,
+ group_obj)
{
- json_t *denom_keys_array;
+ /* 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;
- denom_keys_array = json_object_get (resp_obj,
- hive[s].name);
-
- EXITIF (NULL == denom_keys_array &&
- ! hive[s].is_optional_age_restriction);
-
- if (NULL == denom_keys_array &&
- hive[s].is_optional_age_restriction)
- continue;
-
- /* if "age_restricted_denoms" exists, age-restriction better be enabled
- * (that is: mask non-zero) */
- EXITIF (NULL != denom_keys_array &&
- hive[s].is_optional_age_restriction &&
- 0 == key_data->age_mask.mask);
-
- EXITIF (JSON_ARRAY != json_typeof (denom_keys_array));
-
- json_array_foreach (denom_keys_array, index, denom_key_obj) {
- struct TALER_EXCHANGE_DenomPublicKey dk;
+ 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)
+ {
+ /* 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;
- memset (&dk,
- 0,
- sizeof (dk));
EXITIF (GNUNET_SYSERR ==
- parse_json_denomkey (&dk,
- check_sig,
- denom_key_obj,
- &key_data->master_pub,
- hash_context));
-
- /* Mark age restriction according where we got this denomination from,
- * "denoms" or "age_restricted_denoms" */
- if (hive[s].is_optional_age_restriction)
- dk.age_restricted = true;
-
+ 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++)
@@ -909,6 +1152,7 @@ decode_keys_json (const json_t *resp_obj,
break;
}
}
+
if (found)
{
/* 0:0:0 did not support /keys cherry picking */
@@ -916,10 +1160,14 @@ decode_keys_json (const json_t *resp_obj,
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" */
@@ -928,23 +1176,18 @@ decode_keys_json (const json_t *resp_obj,
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;
bool found = false;
@@ -1003,163 +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 TALER_DenominationHash 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 = {
- .purpose.size = htonl (sizeof (ks)),
- .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET),
- .list_issue_date = GNUNET_TIME_timestamp_hton (key_data->list_issue_date)
- };
+ struct GNUNET_HashContext *hash_context;
+ struct GNUNET_HashCode hc;
+ 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,
- &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++)
- TALER_denom_pub_free (&key_data->denom_keys[i].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 (key_data->version);
- key_data->version = NULL;
- GNUNET_free (key_data->currency);
- key_data->currency = NULL;
-}
-
-
-/**
- * Initiate download of /keys from the exchange.
- *
- * @param cls exchange where to download /keys from
- */
-static void
-request_keys (void *cls);
-
-
-void
-TALER_EXCHANGE_set_last_denom (struct TALER_EXCHANGE_Handle *exchange,
- struct GNUNET_TIME_Timestamp last_denom_new)
-{
- TALER_LOG_DEBUG (
- "Application explicitly set last denomination validity to %s\n",
- GNUNET_TIME_timestamp2s (last_denom_new));
- exchange->key_data.last_denom_issue_date = last_denom_new;
-}
-
-
-struct GNUNET_TIME_Timestamp
-TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange,
- enum TALER_EXCHANGE_CheckKeysFlags flags)
-{
- bool force_download = 0 != (flags & TALER_EXCHANGE_CKF_FORCE_DOWNLOAD);
- bool pull_all_keys = 0 != (flags & TALER_EXCHANGE_CKF_PULL_ALL_KEYS);
-
- if (NULL != exchange->kr)
- return GNUNET_TIME_UNIT_ZERO_TS;
-
- if (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 ( (! force_download) &&
- (GNUNET_TIME_absolute_is_future (
- exchange->key_data_expiration.abs_time)) )
- 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_TS;
-}
-
-
-/**
* Callback used when downloading the reply to a /keys request
* is complete.
*
@@ -1172,132 +1346,122 @@ 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_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ 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 and expiration %s.\n",
- kr->url,
+ gkh->url,
response_code,
- GNUNET_TIME_timestamp2s (kr->expire));
- if (GNUNET_TIME_absolute_is_past (kr->expire.abs_time))
+ GNUNET_TIME_timestamp2s (gkh->expire));
+ if (GNUNET_TIME_absolute_is_past (gkh->expire.abs_time))
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Exchange failed to give expiration time, assuming in %s\n",
- GNUNET_TIME_relative2s (DEFAULT_EXPIRATION,
- true));
- kr->expire
+ 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));
}
- kd_old = exchange->key_data;
- memset (&kd,
- 0,
- sizeof (struct TALER_EXCHANGE_Keys));
- vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR;
switch (response_code)
{
case 0:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to receive /keys response from exchange %s\n",
- exchange->url);
- free_keys_request (kr);
- exchange->keys_error_count++;
- 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;
+ gkh->exchange_url);
+ break;
case MHD_HTTP_OK:
- exchange->keys_error_count = 0;
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++)
- TALER_denom_pub_deep_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++)
+ kd = GNUNET_new (struct TALER_EXCHANGE_Keys);
+ kd->exchange_url = GNUNET_strdup (gkh->exchange_url);
+ if (NULL != gkh->prev_keys)
{
- 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));
- }
+ 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++)
+ {
+ const struct TALER_EXCHANGE_AuditorInformation *aold =
+ &kd_old->auditors[i];
+ struct TALER_EXCHANGE_AuditorInformation *anew = &kd->auditors[i];
- /* Old auditors got just copied into new ones. */
+ anew->auditor_pub = aold->auditor_pub;
+ anew->auditor_url = GNUNET_strdup (aold->auditor_url);
+ GNUNET_array_grow (anew->denom_keys,
+ anew->num_denom_keys,
+ aold->num_denom_keys);
+ GNUNET_memcpy (
+ anew->denom_keys,
+ aold->denom_keys,
+ aold->num_denom_keys
+ * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo));
+ }
+ }
+ /* Now decode fresh /keys response */
if (GNUNET_OK !=
decode_keys_json (j,
true,
- &kd,
- &vc))
+ kd,
+ &kresp.details.ok.compat))
{
TALER_LOG_ERROR ("Could not decode /keys response\n");
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- for (unsigned int i = 0; i<kd.num_auditors; i++)
- {
- struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i];
-
- GNUNET_array_grow (anew->denom_keys,
- anew->num_denom_keys,
- 0);
- GNUNET_free (anew->auditor_url);
- }
- GNUNET_free (kd.auditors);
- kd.auditors = NULL;
- kd.num_auditors = 0;
- for (unsigned int i = 0; i<kd_old.num_denom_keys; i++)
- TALER_denom_pub_free (&kd.denom_keys[i].key);
- GNUNET_array_grow (kd.denom_keys,
- kd.denom_keys_size,
- 0);
- kd.num_denom_keys = 0;
+ 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:
@@ -1305,108 +1469,36 @@ keys_completed_cb (void *cls,
case MHD_HTTP_NOT_FOUND:
if (NULL == j)
{
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = TALER_ErrorCode_get_hint (hr.ec);
+ kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
}
else
{
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ kresp.hr.ec = TALER_JSON_get_error_code (j);
+ kresp.hr.hint = TALER_JSON_get_error_hint (j);
}
break;
default:
- if (MHD_HTTP_GATEWAY_TIMEOUT == response_code)
- exchange->keys_error_count++;
if (NULL == j)
{
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = TALER_ErrorCode_get_hint (hr.ec);
+ kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
}
else
{
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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) hr.ec);
+ (int) kresp.hr.ec);
break;
}
- exchange->key_data = kd;
- if (GNUNET_TIME_absolute_is_past (
- exchange->key_data.last_denom_issue_date.abs_time))
- TALER_LOG_WARNING ("Last DK issue date from exchange is in the past: %s\n",
- GNUNET_TIME_timestamp2s (
- exchange->key_data.last_denom_issue_date));
- else
- TALER_LOG_DEBUG ("Last DK issue date updated to: %s\n",
- GNUNET_TIME_timestamp2s (
- 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)
- {
- json_decref (exchange->key_data_raw);
- exchange->key_data_raw = NULL;
- }
- free_key_data (&kd_old);
- /* notify application that we failed */
- exchange->cert_cb (exchange->cert_cb_cls,
- &hr,
- NULL,
- vc);
- return;
- }
-
- 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,
- &hr,
- &exchange->key_data,
- vc);
- free_key_data (&kd_old);
-}
-
-
-/* ********************* library internal API ********* */
-
-
-struct GNUNET_CURL_Context *
-TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h)
-{
- return h->ctx;
-}
-
-
-enum GNUNET_GenericReturnValue
-TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h)
-{
- return (MHS_CERT == h->state) ? GNUNET_YES : GNUNET_NO;
-}
-
-
-char *
-TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h,
- const char *path)
-{
- GNUNET_assert ('/' == path[0]);
- return TALER_url_join (h->url,
- path + 1,
- NULL);
+ gkh->cert_cb (gkh->cert_cb_cls,
+ &kresp,
+ kd);
+ TALER_EXCHANGE_get_keys_cancel (gkh);
}
@@ -1420,7 +1512,7 @@ TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h,
* Parse HTTP timestamp.
*
* @param dateline header to parse header
- * @param at where to write the result
+ * @param[out] at where to write the result
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
@@ -1520,7 +1612,7 @@ parse_date_string (const char *dateline,
* @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
@@ -1529,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;
@@ -1541,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))
@@ -1556,114 +1652,462 @@ header_cb (char *buffer,
}
-/* ********************* public API ******************* */
+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)
+{
+ struct TALER_EXCHANGE_GetKeysHandle *gkh;
+ CURL *eh;
+ char last_date[80] = { 0 };
+ TALER_LOG_DEBUG ("Connecting to the exchange (%s)\n",
+ url);
+ 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_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);
+ }
+ 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",
+ gkh->url);
+ eh = TALER_EXCHANGE_curl_easy_get_ (gkh->url);
+ if (NULL == eh)
+ {
+ 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,
+ 1));
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT,
+ 120 /* seconds */));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERFUNCTION,
+ &header_cb));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERDATA,
+ gkh));
+ gkh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
+ eh,
+ &keys_completed_cb,
+ gkh);
+ return gkh;
+}
-/**
- * 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)
+
+void
+TALER_EXCHANGE_get_keys_cancel (
+ struct TALER_EXCHANGE_GetKeysHandle *gkh)
{
- enum TALER_EXCHANGE_VersionCompatibility vc;
- json_t *keys;
+ if (NULL != gkh->job)
+ {
+ GNUNET_CURL_job_cancel (gkh->job);
+ gkh->job = NULL;
+ }
+ TALER_EXCHANGE_keys_decref (gkh->prev_keys);
+ GNUNET_free (gkh->exchange_url);
+ GNUNET_free (gkh->url);
+ GNUNET_free (gkh);
+}
+
+
+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 ( (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;
+}
+
+
+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 ==
+ TALER_denom_pub_cmp (pk,
+ &keys->denom_keys[i].key))
+ return &keys->denom_keys[i];
+ return NULL;
+}
+
+
+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)
+{
+ struct TALER_EXCHANGE_DenomPublicKey *copy;
+
+ copy = GNUNET_new (struct TALER_EXCHANGE_DenomPublicKey);
+ *copy = *key;
+ TALER_denom_pub_copy (&copy->key,
+ &key->key);
+ return copy;
+}
+
+
+void
+TALER_EXCHANGE_destroy_denomination_key (
+ struct TALER_EXCHANGE_DenomPublicKey *key)
+{
+ TALER_denom_pub_free (&key->key);
+ GNUNET_free (key);
+}
+
+
+const struct TALER_EXCHANGE_DenomPublicKey *
+TALER_EXCHANGE_get_denomination_key_by_hash (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_DenominationHashP *hc)
+{
+ for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+ if (0 == GNUNET_memcmp (hc,
+ &keys->denom_keys[i].h_key))
+ return &keys->denom_keys[i];
+ return NULL;
+}
+
+
+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;
+ struct GNUNET_TIME_Timestamp expire
+ = GNUNET_TIME_UNIT_ZERO_TS;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_uint32 ("version",
&version),
- GNUNET_JSON_spec_json ("keys",
- &keys),
- GNUNET_JSON_spec_string ("exchange_url",
+ GNUNET_JSON_spec_object_const ("keys",
+ &jkeys),
+ TALER_JSON_spec_web_url ("exchange_url",
&url),
- GNUNET_JSON_spec_timestamp ("expire",
- &expire),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("expire",
+ &expire),
+ NULL),
GNUNET_JSON_spec_end ()
};
- struct TALER_EXCHANGE_Keys key_data;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .ec = TALER_EC_NONE,
- .http_status = MHD_HTTP_OK,
- .reply = data
- };
+ struct TALER_EXCHANGE_Keys *keys;
+ enum TALER_EXCHANGE_VersionCompatibility compat;
- if (NULL == data)
- return;
+ if (NULL == j)
+ return NULL;
if (GNUNET_OK !=
- GNUNET_JSON_parse (data,
+ GNUNET_JSON_parse (j,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
- return;
+ return NULL;
}
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;
+ return NULL; /* unsupported version */
}
- memset (&key_data,
- 0,
- sizeof (struct TALER_EXCHANGE_Keys));
+ keys = GNUNET_new (struct TALER_EXCHANGE_Keys);
if (GNUNET_OK !=
- decode_keys_json (keys,
+ decode_keys_json (jkeys,
false,
- &key_data,
- &vc))
+ keys,
+ &compat))
{
GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return;
+ return NULL;
}
- /* 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,
- &hr,
- &exchange->key_data,
- vc);
- GNUNET_JSON_parse_free (spec);
+ keys->rc = 1;
+ keys->key_data_expiration = expire;
+ keys->exchange_url = GNUNET_strdup (url);
+ return keys;
+}
+
+
+/**
+ * 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[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)
+ */
+static enum GNUNET_GenericReturnValue
+add_grp (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ 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;
+}
+
+
+/**
+ * Convert array of account restrictions @a ars to 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_serialize_data (struct TALER_EXCHANGE_Handle *exchange)
+TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd)
{
- const struct TALER_EXCHANGE_Keys *kd = &exchange->key_data;
struct GNUNET_TIME_Timestamp now;
json_t *keys;
json_t *signkeys;
- json_t *denoms;
+ 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 ();
- if (NULL == signkeys)
- {
- GNUNET_break (0);
- return NULL;
- }
+ 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];
@@ -1684,63 +2128,96 @@ TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange)
sk->valid_until),
GNUNET_JSON_pack_timestamp ("stamp_end",
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;
+ GNUNET_assert (NULL != signkey);
+ GNUNET_assert (0 ==
+ json_array_append_new (signkeys,
+ signkey));
}
- for (unsigned int i = 0; i<kd->num_denom_keys; i++)
+
+ denominations_by_group = json_array ();
+ GNUNET_assert (NULL != denominations_by_group);
{
- const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i];
- json_t *denom;
+ struct GNUNET_CONTAINER_MultiHashMap *dbg;
- if (GNUNET_TIME_timestamp_cmp (now,
- >,
- dk->expire_deposit))
- continue; /* skip keys that have expired */
- 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),
- TALER_JSON_pack_amount ("value",
- &dk->value),
- TALER_JSON_pack_amount ("fee_withdraw",
- &dk->fee_withdraw),
- TALER_JSON_pack_amount ("fee_deposit",
- &dk->fee_deposit),
- TALER_JSON_pack_amount ("fee_refresh",
- &dk->fee_refresh),
- TALER_JSON_pack_amount ("fee_refund",
- &dk->fee_refund),
- GNUNET_JSON_pack_data_auto ("master_sig",
- &dk->master_sig),
- TALER_JSON_pack_denom_pub ("denom_pub",
- &dk->key));
- GNUNET_assert (0 ==
- json_array_append_new (denoms,
- denom));
+ 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++)
@@ -1750,14 +2227,7 @@ TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange)
json_t *adenoms;
adenoms = json_array ();
- if (NULL == adenoms)
- {
- GNUNET_break (0);
- json_decref (denoms);
- json_decref (signkeys);
- json_decref (auditors);
- return NULL;
- }
+ GNUNET_assert (NULL != adenoms);
for (unsigned int j = 0; j<ai->num_denom_keys; j++)
{
const struct TALER_EXCHANGE_AuditorDenominationInfo *adi =
@@ -1766,6 +2236,7 @@ TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange)
&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))
@@ -1792,349 +2263,210 @@ TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange)
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_array_steal ("denoms",
- denoms),
+ 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));
+ 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_timestamp ("expire",
- exchange->key_data_expiration),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("expire",
+ kd->key_data_expiration)),
GNUNET_JSON_pack_string ("exchange_url",
- exchange->url),
+ kd->exchange_url),
GNUNET_JSON_pack_object_steal ("keys",
keys));
}
-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_Handle *exchange;
- va_list ap;
- enum TALER_EXCHANGE_Option opt;
-
- TALER_LOG_DEBUG ("Connecting to the exchange (%s)\n",
- url);
- /* Disable 100 continue processing */
- GNUNET_break (GNUNET_OK ==
- GNUNET_CURL_append_header (ctx,
- MHD_HTTP_HEADER_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;
-}
-
-
-/**
- * Compute the network timeout for the next request to /keys.
- *
- * @param exchange the exchange handle
- * @returns the timeout in seconds (for use by CURL)
- */
-static long
-get_keys_timeout_seconds (struct TALER_EXCHANGE_Handle *exchange)
-{
- unsigned int kec;
-
- /* if retry counter >= 8, do not bother to go further, we
- stop the exponential back-off at 128 anyway. */
- kec = GNUNET_MIN (7,
- exchange->keys_error_count);
- return GNUNET_MIN (120,
- 5 + (1L << kec));
-}
-
-
-/**
- * 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))
- {
- TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n",
- GNUNET_TIME_timestamp2s (
- 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_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);
- if (NULL == kr->url)
- {
- struct TALER_EXCHANGE_HttpResponse hr = {
- .ec = TALER_EC_GENERIC_CONFIGURATION_INVALID
- };
-
- GNUNET_free (kr);
- exchange->keys_error_count++;
- exchange->state = MHS_FAILED;
- exchange->cert_cb (exchange->cert_cb_cls,
- &hr,
- NULL,
- TALER_EXCHANGE_VC_PROTOCOL_ERROR);
- return;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Requesting keys with URL `%s'.\n",
- kr->url);
- eh = TALER_EXCHANGE_curl_easy_get_ (kr->url);
- if (NULL == eh)
- {
- GNUNET_free (kr->url);
- GNUNET_free (kr);
- 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 (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_VERBOSE,
- 0));
- GNUNET_break (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_TIMEOUT,
- get_keys_timeout_seconds (exchange)));
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_HEADERFUNCTION,
- &header_cb));
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_HEADERDATA,
- kr));
- kr->job = GNUNET_CURL_job_add_with_ct_json (exchange->ctx,
- eh,
- &keys_completed_cb,
- kr);
- exchange->kr = kr;
-}
-
-
-void
-TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange)
-{
- struct TEAH_AuditorListEntry *ale;
-
- while (NULL != (ale = exchange->auditors_head))
- {
- 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;
- }
- if (NULL != exchange->retry_task)
- {
- GNUNET_SCHEDULER_cancel (exchange->retry_task);
- exchange->retry_task = NULL;
- }
- GNUNET_free (exchange->url);
- GNUNET_free (exchange);
-}
-
-
-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 ( (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;
-}
-
-
-const char *
-TALER_EXCHANGE_get_base_url (const struct TALER_EXCHANGE_Handle *exchange)
-{
- return exchange->url;
-}
-
-
-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 ==
- TALER_denom_pub_cmp (pk,
- &keys->denom_keys[i].key))
- return &keys->denom_keys[i];
- return NULL;
-}
-
-
-struct TALER_EXCHANGE_DenomPublicKey *
-TALER_EXCHANGE_copy_denomination_key (
- const struct TALER_EXCHANGE_DenomPublicKey *key)
-{
- struct TALER_EXCHANGE_DenomPublicKey *copy;
-
- copy = GNUNET_new (struct TALER_EXCHANGE_DenomPublicKey);
- *copy = *key;
- TALER_denom_pub_deep_copy (&copy->key,
- &key->key);
- return copy;
-}
-
-
-void
-TALER_EXCHANGE_destroy_denomination_key (
- struct TALER_EXCHANGE_DenomPublicKey *key)
-{
- TALER_denom_pub_free (&key->key);
- GNUNET_free (key);
-}
-
-
-const struct TALER_EXCHANGE_DenomPublicKey *
-TALER_EXCHANGE_get_denomination_key_by_hash (
- const struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_DenominationHash *hc)
-{
- for (unsigned int i = 0; i<keys->num_denom_keys; i++)
- if (0 == GNUNET_memcmp (hc,
- &keys->denom_keys[i].h_key))
- return &keys->denom_keys[i];
- return NULL;
-}
-
-
-const struct TALER_EXCHANGE_Keys *
-TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange)
-{
- (void) TALER_EXCHANGE_check_keys_current (exchange,
- TALER_EXCHANGE_CKF_NONE);
- return &exchange->key_data;
-}
-
-
-json_t *
-TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange)
-{
- (void) TALER_EXCHANGE_check_keys_current (exchange,
- TALER_EXCHANGE_CKF_NONE);
- return json_deep_copy (exchange->key_data_raw);
-}
-
-
/* end of exchange_api_handle.c */
diff --git a/src/lib/exchange_api_handle.h b/src/lib/exchange_api_handle.h
index 569c723c4..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,183 +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;
-};
-
-/**
- * Stages of initialization for the `struct TALER_EXCHANGE_Handle`
- */
-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
-};
-
-
-/**
- * Handle to the exchange
- */
-struct TALER_EXCHANGE_Handle
-{
- /**
- * The context of this handle
- */
- struct GNUNET_CURL_Context *ctx;
-
- /**
- * The URL of the exchange (i.e. "http://exchange.taler.net/")
- */
- char *url;
-
- /**
- * Function to call with the exchange's certification data,
- * NULL if this has already been done.
- */
- TALER_EXCHANGE_CertificationCallback 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;
-
- /**
- * Key data of the exchange, only valid if
- * @e handshake_complete is past stage #MHS_CERT.
- */
- struct TALER_EXCHANGE_Keys key_data;
-
- /**
- * Retry /keys frequency.
- */
- struct GNUNET_TIME_Relative retry_delay;
-
- /**
- * When does @e key_data expire?
- */
- struct GNUNET_TIME_Timestamp key_data_expiration;
-
- /**
- * Number of subsequent failed requests to /keys.
- *
- * Used to compute the CURL timeout for the request.
- */
- unsigned int keys_error_count;
-
- /**
- * Number of subsequent failed requests to /wire.
- *
- * Used to compute the CURL timeout for the request.
- */
- unsigned int wire_error_count;
-
- /**
- * Stage of the exchange's initialization routines.
- */
- enum ExchangeHandleState state;
-
-};
-
/**
* 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 hr HTTP response
- */
-void
-TEAH_acc_confirmation_cb (void *cls,
- const struct TALER_AUDITOR_HttpResponse *hr);
+typedef void
+(*TEAH_AuditorCallback)(
+ void *cls,
+ const char *auditor_url,
+ const struct TALER_AuditorPublicKeyP *auditor_pub);
/**
@@ -203,54 +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);
-
+TEAH_get_auditors_for_dc (
+ struct TALER_EXCHANGE_Keys *keys,
+ 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);
-
-
-/**
- * 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
- */
-enum GNUNET_GenericReturnValue
-TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h);
-
-/**
- * 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
- */
-enum GNUNET_GenericReturnValue
-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
index bf7b0bf40..5d3b3792b 100644
--- a/src/lib/exchange_api_kyc_check.c
+++ b/src/lib/exchange_api_kyc_check.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -37,14 +37,14 @@ struct TALER_EXCHANGE_KycCheckHandle
{
/**
- * The connection to exchange this request handle will use
+ * The url for this request.
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ char *url;
/**
- * The url for this request.
+ * Keys of the exchange.
*/
- char *url;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* Handle for the request.
@@ -64,7 +64,8 @@ struct TALER_EXCHANGE_KycCheckHandle
/**
* Hash of the payto:// URL that is being KYC'ed.
*/
- struct TALER_PaytoHash h_payto;
+ struct TALER_PaytoHashP h_payto;
+
};
@@ -95,22 +96,20 @@ handle_kyc_check_finished (void *cls,
break;
case MHD_HTTP_OK:
{
+ const json_t *kyc_details;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &ks.details.kyc_ok.exchange_sig),
+ &ks.details.ok.exchange_sig),
GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &ks.details.kyc_ok.exchange_pub),
+ &ks.details.ok.exchange_pub),
GNUNET_JSON_spec_timestamp ("now",
- &ks.details.kyc_ok.timestamp),
+ &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 ()
};
- const struct TALER_EXCHANGE_Keys *key_state;
- struct TALER_ExchangeAccountSetupSuccessPS kyc_purpose = {
- .purpose.size = htonl (sizeof (kyc_purpose)),
- .purpose.purpose = htonl (
- TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS),
- .h_payto = kch->h_payto
- };
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
@@ -122,12 +121,10 @@ handle_kyc_check_finished (void *cls,
ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
- kyc_purpose.timestamp = GNUNET_TIME_timestamp_hton (
- ks.details.kyc_ok.timestamp);
- key_state = TALER_EXCHANGE_get_keys (kch->exchange);
+ ks.details.ok.kyc_details = kyc_details;
if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- &ks.details.kyc_ok.exchange_pub))
+ TALER_EXCHANGE_test_signing_key (kch->keys,
+ &ks.details.ok.exchange_pub))
{
GNUNET_break_op (0);
ks.http_status = 0;
@@ -137,11 +134,12 @@ handle_kyc_check_finished (void *cls,
}
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (
- TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS,
- &kyc_purpose,
- &ks.details.kyc_ok.exchange_sig.eddsa_signature,
- &ks.details.kyc_ok.exchange_pub.eddsa_pub))
+ 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;
@@ -158,8 +156,10 @@ handle_kyc_check_finished (void *cls,
case MHD_HTTP_ACCEPTED:
{
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("kyc_url",
- &ks.details.kyc_url),
+ 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 ()
};
@@ -186,12 +186,37 @@ handle_kyc_check_finished (void *cls,
/* 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:
+ 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
@@ -214,24 +239,21 @@ handle_kyc_check_finished (void *cls,
struct TALER_EXCHANGE_KycCheckHandle *
-TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *exchange,
- uint64_t payment_target,
- const struct TALER_PaytoHash *h_payto,
- struct GNUNET_TIME_Relative timeout,
- TALER_EXCHANGE_KycStatusCallback cb,
- void *cb_cls)
+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;
- struct GNUNET_CURL_Context *ctx;
char *arg_str;
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
{
char payto_str[sizeof (*h_payto) * 2];
char *end;
@@ -246,18 +268,19 @@ TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *exchange,
timeout_ms = timeout.rel_value_us
/ GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
GNUNET_asprintf (&arg_str,
- "/kyc-check/%llu?h_payto=%s&timeout_ms=%llu",
- (unsigned long long) payment_target,
+ "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->exchange = exchange;
kch->h_payto = *h_payto;
kch->cb = cb;
kch->cb_cls = cb_cls;
- kch->url = TEAH_path_to_url (exchange,
- arg_str);
+ kch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
GNUNET_free (arg_str);
if (NULL == kch->url)
{
@@ -272,7 +295,7 @@ TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *exchange,
GNUNET_free (kch);
return NULL;
}
- ctx = TEAH_handle_to_context (exchange);
+ kch->keys = TALER_EXCHANGE_keys_incref (keys);
kch->job = GNUNET_CURL_job_add_with_ct_json (ctx,
eh,
&handle_kyc_check_finished,
@@ -289,6 +312,7 @@ TALER_EXCHANGE_kyc_check_cancel (struct TALER_EXCHANGE_KycCheckHandle *kch)
GNUNET_CURL_job_cancel (kch->job);
kch->job = NULL;
}
+ TALER_EXCHANGE_keys_decref (kch->keys);
GNUNET_free (kch->url);
GNUNET_free (kch);
}
diff --git a/src/lib/exchange_api_kyc_proof.c b/src/lib/exchange_api_kyc_proof.c
index f2ca24eb1..e7cc9c4cf 100644
--- a/src/lib/exchange_api_kyc_proof.c
+++ b/src/lib/exchange_api_kyc_proof.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021, 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -37,11 +37,6 @@ struct TALER_EXCHANGE_KycProofHandle
{
/**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
* The url for this request.
*/
char *url;
@@ -140,35 +135,43 @@ handle_kyc_proof_finished (void *cls,
struct TALER_EXCHANGE_KycProofHandle *
-TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *exchange,
- uint64_t payment_target,
- const char *code,
- const char *state,
- TALER_EXCHANGE_KycProofCallback cb,
- void *cb_cls)
+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;
- struct GNUNET_CURL_Context *ctx;
char *arg_str;
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
+ if (NULL == args)
+ args = "";
+ else
+ GNUNET_assert (args[0] == '&');
{
- GNUNET_break (0);
- return NULL;
+ 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);
}
- /* TODO: any escaping of code/state needed??? */
- GNUNET_asprintf (&arg_str,
- "/kyc-proof/%llu?code=%s&state=%s",
- (unsigned long long) payment_target,
- code,
- state);
kph = GNUNET_new (struct TALER_EXCHANGE_KycProofHandle);
- kph->exchange = exchange;
kph->cb = cb;
kph->cb_cls = cb_cls;
- kph->url = TEAH_path_to_url (exchange,
- arg_str);
+ kph->url = TALER_url_join (url,
+ arg_str,
+ NULL);
GNUNET_free (arg_str);
if (NULL == kph->url)
{
@@ -189,7 +192,6 @@ TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *exchange,
curl_easy_setopt (kph->eh,
CURLOPT_FOLLOWLOCATION,
0L));
- ctx = TEAH_handle_to_context (exchange);
kph->job = GNUNET_CURL_job_add_raw (ctx,
kph->eh,
NULL,
diff --git a/src/lib/exchange_api_kyc_wallet.c b/src/lib/exchange_api_kyc_wallet.c
index 4a41fd598..7197694ae 100644
--- a/src/lib/exchange_api_kyc_wallet.c
+++ b/src/lib/exchange_api_kyc_wallet.c
@@ -43,11 +43,6 @@ struct TALER_EXCHANGE_KycWalletHandle
struct TALER_CURL_PostContext ctx;
/**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
* The url for this request.
*/
char *url;
@@ -95,11 +90,28 @@ handle_kyc_wallet_finished (void *cls,
case 0:
ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
- case MHD_HTTP_OK:
+ 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_uint64 ("payment_target_uuid",
- &ks.payment_target_uuid),
+ 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 ()
};
@@ -115,19 +127,6 @@ handle_kyc_wallet_finished (void *cls,
}
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_UNAUTHORIZED:
- 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_INTERNAL_SERVER_ERROR:
ks.ec = TALER_JSON_get_error_code (j);
/* Server had an internal issue; we should retry, but this API
@@ -150,49 +149,45 @@ handle_kyc_wallet_finished (void *cls,
struct TALER_EXCHANGE_KycWalletHandle *
-TALER_EXCHANGE_kyc_wallet (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- TALER_EXCHANGE_KycWalletCallback cb,
- void *cb_cls)
+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 GNUNET_CURL_Context *ctx;
struct TALER_ReservePublicKeyP reserve_pub;
struct TALER_ReserveSignatureP reserve_sig;
- struct GNUNET_CRYPTO_EccSignaturePurpose purpose = {
- .size = htonl (sizeof (purpose)),
- .purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_SETUP)
- };
- /* FIXME: move to util/wallet-signatures.c! */
GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
&reserve_pub.eddsa_pub);
-
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign_ (&reserve_priv->eddsa_priv,
- &purpose,
- &reserve_sig.eddsa_signature));
+ 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->exchange = exchange;
kwh->cb = cb;
kwh->cb_cls = cb_cls;
- kwh->url = TEAH_path_to_url (exchange,
- "/kyc-wallet");
+ kwh->url = TALER_url_join (url,
+ "kyc-wallet",
+ NULL);
if (NULL == kwh->url)
{
json_decref (req);
GNUNET_free (kwh);
return NULL;
}
- ctx = TEAH_handle_to_context (exchange);
eh = TALER_EXCHANGE_curl_easy_get_ (kwh->url);
if ( (NULL == eh) ||
(GNUNET_OK !=
diff --git a/src/lib/exchange_api_link.c b/src/lib/exchange_api_link.c
index ec085b533..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-2021 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,36 +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
* @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 enum GNUNET_GenericReturnValue
parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
const json_t *json,
- uint32_t 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 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[] = {
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 !=
@@ -118,44 +128,76 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
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 */
if (GNUNET_OK !=
- TALER_denom_sig_unblind (sig,
+ TALER_denom_sig_unblind (&lci->sig,
&bsig,
- &fc.blinding_key,
+ &bks,
+ &c_hash,
+ &alg_values,
&rpub))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- *coin_priv = fc.coin_priv;
/* verify link_sig */
{
- struct TALER_PlanchetDetail pd;
- struct TALER_CoinPubHash c_hash;
struct TALER_CoinSpendPublicKeyP old_coin_pub;
- struct TALER_BlindedCoinHash coin_envelope_hash;
+ struct TALER_BlindedCoinHashP coin_envelope_hash;
GNUNET_CRYPTO_eddsa_key_get_public (&lh->coin_priv.eddsa_priv,
&old_coin_pub.eddsa_pub);
- if (GNUNET_OK !=
- TALER_planchet_prepare (&rpub,
- &fc,
- &c_hash,
- &pd))
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- GNUNET_CRYPTO_hash (pd.coin_ev,
- pd.coin_ev_size,
- &coin_envelope_hash.hash);
+ TALER_coin_ev_hash (&pd.blinded_planchet,
+ &pd.denom_pub_hash,
+ &coin_envelope_hash);
if (GNUNET_OK !=
TALER_wallet_link_verify (&pd.denom_pub_hash,
trans_pub,
@@ -164,16 +206,16 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
&link_sig))
{
GNUNET_break_op (0);
- GNUNET_free (pd.coin_ev);
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
- GNUNET_free (pd.coin_ev);
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
}
/* clean up */
- TALER_denom_pub_deep_copy (pub,
- &rpub);
+ TALER_denom_pub_copy (&lci->pub,
+ &rpub);
GNUNET_JSON_parse_free (spec);
return GNUNET_OK;
}
@@ -194,9 +236,9 @@ parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
unsigned int session;
unsigned int num_coins;
int ret;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = json,
- .http_status = MHD_HTTP_OK
+ struct TALER_EXCHANGE_LinkResult lr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
};
if (! json_is_array (json))
@@ -218,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 ()
};
@@ -233,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 ()
@@ -277,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;
@@ -308,20 +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,
- &hr,
- num_coins,
- coin_privs,
- sigs,
- pubs);
+ &lr);
lh->link_cb = NULL;
ret = GNUNET_OK;
}
@@ -335,8 +357,10 @@ parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
GNUNET_assert (off_coin <= num_coins);
for (i = 0; i<off_coin; i++)
{
- TALER_denom_sig_free (&sigs[i]);
- TALER_denom_pub_free (&pubs[i]);
+ 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;
@@ -358,16 +382,16 @@ handle_link_finished (void *cls,
{
struct TALER_EXCHANGE_LinkHandle *lh = cls;
const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ struct TALER_EXCHANGE_LinkResult lr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
};
lh->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
@@ -375,72 +399,63 @@ handle_link_finished (void *cls,
j))
{
GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_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:
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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:
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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:
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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);
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 link\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) lr.hr.ec);
break;
}
if (NULL != lh->link_cb)
lh->link_cb (lh->link_cb_cls,
- &hr,
- 0,
- NULL,
- NULL,
- NULL);
+ &lr);
TALER_EXCHANGE_link_cancel (lh);
}
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);
{
@@ -455,16 +470,17 @@ TALER_EXCHANGE_link (struct TALER_EXCHANGE_Handle *exchange,
*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);
@@ -478,7 +494,6 @@ 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_with_ct_json (ctx,
eh,
&handle_link_finished,
@@ -495,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
index bfe60ee79..8bce7f74f 100644
--- a/src/lib/exchange_api_management_auditor_disable.c
+++ b/src/lib/exchange_api_management_auditor_disable.c
@@ -81,9 +81,9 @@ handle_auditor_disable_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementAuditorDisableResponse adr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
ah->job = NULL;
@@ -92,32 +92,32 @@ handle_auditor_disable_finished (void *cls,
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_CONFLICT:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (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 management auditor disable\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) adr.hr.ec);
break;
}
if (NULL != ah->cb)
{
ah->cb (ah->cb_cls,
- &hr);
+ &adr);
ah->cb = NULL;
}
TALER_EXCHANGE_management_disable_auditor_cancel (ah);
@@ -173,16 +173,17 @@ TALER_EXCHANGE_management_disable_auditor (
GNUNET_JSON_pack_timestamp ("validity_end",
validity_end));
eh = TALER_EXCHANGE_curl_easy_get_ (ah->url);
- GNUNET_assert (NULL != eh);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&ah->post_ctx,
- eh,
- body))
+ 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);
- GNUNET_free (eh);
return NULL;
}
json_decref (body);
diff --git a/src/lib/exchange_api_management_auditor_enable.c b/src/lib/exchange_api_management_auditor_enable.c
index a99449307..41c5049c2 100644
--- a/src/lib/exchange_api_management_auditor_enable.c
+++ b/src/lib/exchange_api_management_auditor_enable.c
@@ -82,9 +82,9 @@ handle_auditor_enable_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementAuditorEnableResponse aer = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
ah->job = NULL;
@@ -93,28 +93,43 @@ handle_auditor_enable_finished (void *cls,
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) aer.hr.ec);
break;
}
if (NULL != ah->cb)
{
ah->cb (ah->cb_cls,
- &hr);
+ &aer);
ah->cb = NULL;
}
TALER_EXCHANGE_management_enable_auditor_cancel (ah);
@@ -163,16 +178,17 @@ TALER_EXCHANGE_management_enable_auditor (
GNUNET_JSON_pack_timestamp ("validity_start",
validity_start));
eh = TALER_EXCHANGE_curl_easy_get_ (ah->url);
- GNUNET_assert (NULL != eh);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&ah->post_ctx,
- eh,
- body))
+ 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);
- GNUNET_free (eh);
return NULL;
}
json_decref (body);
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
index e776082d3..b88ddc205 100644
--- a/src/lib/exchange_api_management_get_keys.c
+++ b/src/lib/exchange_api_management_get_keys.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
@@ -26,7 +26,7 @@
#include "exchange_api_curl_defaults.h"
#include "taler_signatures.h"
#include "taler_curl_lib.h"
-#include "taler_crypto_lib.h"
+#include "taler_util.h"
#include "taler_json_lib.h"
/**
@@ -75,25 +75,32 @@ struct TALER_EXCHANGE_ManagementGetKeysHandle
* @param response the response
* @return #GNUNET_OK if the response was well-formed
*/
-static int
+static enum GNUNET_GenericReturnValue
handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
const json_t *response)
{
- struct TALER_EXCHANGE_FutureKeys fk;
- json_t *sk;
- json_t *dk;
+ 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_json ("future_denoms",
- &dk),
- GNUNET_JSON_spec_json ("future_signkeys",
- &sk),
+ 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),
+ &fk->master_pub),
GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key",
- &fk.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),
+ &fk->signkey_secmod_public_key),
GNUNET_JSON_spec_end ()
};
@@ -105,22 +112,22 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
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,
+ 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,
+ 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++)
+ 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 spec[] = {
+ = &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",
@@ -136,7 +143,7 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
- spec,
+ ispec,
NULL, NULL))
{
GNUNET_break_op (0);
@@ -153,7 +160,7 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
&sign_key->key,
sign_key->valid_from,
duration,
- &fk.signkey_secmod_public_key,
+ &fk->signkey_secmod_public_key,
&sign_key->signkey_secmod_sig))
{
GNUNET_break_op (0);
@@ -162,12 +169,12 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
}
}
}
- for (unsigned int i = 0; i<fk.num_denom_keys; i++)
+ 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];
+ = &fk->denom_keys[i];
const char *section_name;
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount_any ("value",
@@ -213,7 +220,7 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
}
{
- struct TALER_DenominationHash h_denom_pub;
+ struct TALER_DenominationHashP h_denom_pub;
struct GNUNET_TIME_Relative duration
= GNUNET_TIME_absolute_get_difference (
denom_key->valid_from.abs_time,
@@ -221,20 +228,21 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
TALER_denom_pub_hash (&denom_key->key,
&h_denom_pub);
- switch (denom_key->key.cipher)
+ switch (denom_key->key.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
{
struct TALER_RsaPubHashP h_rsa;
- TALER_rsa_pub_hash (denom_key->key.details.rsa_public_key,
- &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,
+ &fk->denom_secmod_public_key,
&denom_key->denom_secmod_sig))
{
GNUNET_break_op (0);
@@ -243,32 +251,45 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
}
}
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;
}
}
- GNUNET_JSON_parse_free (spec);
if (! ok)
break;
}
if (ok)
{
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = MHD_HTTP_OK,
- .reply = response
- };
-
gh->cb (gh->cb_cls,
- &hr,
- &fk);
+ &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);
- GNUNET_JSON_parse_free (spec);
+ 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;
}
@@ -288,9 +309,9 @@ handle_get_keys_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementGetKeysHandle *gh = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementGetKeysResponse gkr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
gh->job = NULL;
@@ -308,29 +329,43 @@ handle_get_keys_finished (void *cls,
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)
{
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ gkr.hr.ec = TALER_JSON_get_error_code (json);
+ gkr.hr.hint = TALER_JSON_get_error_hint (json);
}
else
{
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = TALER_ErrorCode_get_hint (hr.ec);
+ 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) hr.ec);
+ (int) gkr.hr.ec);
break;
}
if (NULL != gh->cb)
{
gh->cb (gh->cb_cls,
- &hr,
- NULL);
+ &gkr);
gh->cb = NULL;
}
TALER_EXCHANGE_get_management_keys_cancel (gh);
diff --git a/src/lib/exchange_api_management_post_extensions.c b/src/lib/exchange_api_management_post_extensions.c
index 862ff7117..00d1c5e3f 100644
--- a/src/lib/exchange_api_management_post_extensions.c
+++ b/src/lib/exchange_api_management_post_extensions.c
@@ -1,19 +1,19 @@
/*
- This file is part of TALER
- Copyright (C) 2015-2021 Taler Systems SA
+ 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 free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
- TALER is distributed in the hope that it will be useful, but WITHOUT 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 lib/exchange_api_management_post_extensions.c
* @brief functions to handle the settings for extensions (p2p and age restriction)
@@ -23,6 +23,7 @@
#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"
@@ -82,9 +83,9 @@ handle_post_extensions_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementPostExtensionsResponse per = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
ph->job = NULL;
@@ -93,31 +94,42 @@ handle_post_extensions_finished (void *cls,
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ per.hr.ec = TALER_JSON_get_error_code (json);
+ per.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) per.hr.ec);
break;
}
if (NULL != ph->cb)
{
ph->cb (ph->cb_cls,
- &hr);
+ &per);
ph->cb = NULL;
}
- TALER_EXCHANGE_post_management_extensions_cancel (ph);
+ TALER_EXCHANGE_management_post_extensions_cancel (ph);
}
@@ -125,15 +137,13 @@ struct TALER_EXCHANGE_ManagementPostExtensionsHandle *
TALER_EXCHANGE_management_post_extensions (
struct GNUNET_CURL_Context *ctx,
const char *url,
- const struct TALER_EXCHANGE_ManagementPostExtensionsData *pkd,
- TALER_EXCHANGE_ManagementPostKeysCallback cb,
+ 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;
- json_t *extensions = NULL;
- json_t *extensions_sigs = NULL;
ph = GNUNET_new (struct TALER_EXCHANGE_ManagementPostExtensionsHandle);
ph->cb = cb;
@@ -149,65 +159,31 @@ TALER_EXCHANGE_management_post_extensions (
GNUNET_free (ph);
return NULL;
}
- extensions = json_array ();
- GNUNET_assert (NULL != extensions);
- for (unsigned int i = 0; i<pkd->num_extensions; i++)
- {
- const json_t *config;
- const struct TALER_Extension *ext = &pkd->extensions[i];
-
- config = ext->config_to_json (ext);
-
- GNUNET_assert (NULL != config);
- GNUNET_assert (0 ==
- json_array_append_new (
- extensions,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("extension",
- &ext->name),
- GNUNET_JSON_pack_data_auto ("config",
- config)
- )));
- }
- extensions_sigs = json_array ();
- GNUNET_assert (NULL != extensions_sigs);
- for (unsigned int i = 0; i<pkd->num_extensions; i++)
- {
- const struct TALER_MasterSignatureP *sks
- = &pkd->extensions_sigs[i];
-
- GNUNET_assert (0 ==
- json_array_append_new (
- extensions_sigs,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("extension_sig",
- &sks->eddsa_signature))));
- }
+
body = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_array_steal ("extensions",
- extensions),
- GNUNET_JSON_pack_array_steal ("extensions_sigs",
- extensions_sigs));
- eh = curl_easy_init ();
- GNUNET_assert (NULL != eh);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&ph->post_ctx,
- eh,
- body))
+ 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);
- GNUNET_free (eh);
return NULL;
}
json_decref (body);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Requesting URL '%s'\n",
ph->url);
- GNUNET_assert (CURLE_OK == curl_easy_setopt (eh,
- CURLOPT_URL,
- ph->url));
ph->job = GNUNET_CURL_job_add2 (ctx,
eh,
ph->post_ctx.headers,
@@ -215,7 +191,7 @@ TALER_EXCHANGE_management_post_extensions (
ph);
if (NULL == ph->job)
{
- TALER_EXCHANGE_post_management_extensions_cancel (ph);
+ TALER_EXCHANGE_management_post_extensions_cancel (ph);
return NULL;
}
return ph;
@@ -223,7 +199,7 @@ TALER_EXCHANGE_management_post_extensions (
void
-TALER_EXCHANGE_post_management_extensions_cancel (
+TALER_EXCHANGE_management_post_extensions_cancel (
struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph)
{
if (NULL != ph->job)
diff --git a/src/lib/exchange_api_management_post_keys.c b/src/lib/exchange_api_management_post_keys.c
index d7790599a..a46124d90 100644
--- a/src/lib/exchange_api_management_post_keys.c
+++ b/src/lib/exchange_api_management_post_keys.c
@@ -82,9 +82,9 @@ handle_post_keys_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementPostKeysHandle *ph = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementPostKeysResponse pkr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
ph->job = NULL;
@@ -93,32 +93,32 @@ handle_post_keys_finished (void *cls,
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ pkr.hr.ec = TALER_JSON_get_error_code (json);
+ pkr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) pkr.hr.ec);
break;
}
if (NULL != ph->cb)
{
ph->cb (ph->cb_cls,
- &hr);
+ &pkr);
ph->cb = NULL;
}
TALER_EXCHANGE_post_management_keys_cancel (ph);
@@ -191,16 +191,17 @@ TALER_EXCHANGE_post_management_keys (
GNUNET_JSON_pack_array_steal ("signkey_sigs",
signkey_sigs));
eh = TALER_EXCHANGE_curl_easy_get_ (ph->url);
- GNUNET_assert (NULL != eh);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&ph->post_ctx,
- eh,
- body))
+ 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);
- GNUNET_free (eh);
return NULL;
}
json_decref (body);
diff --git a/src/lib/exchange_api_management_revoke_denomination_key.c b/src/lib/exchange_api_management_revoke_denomination_key.c
index 383d9532e..a57704776 100644
--- a/src/lib/exchange_api_management_revoke_denomination_key.c
+++ b/src/lib/exchange_api_management_revoke_denomination_key.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2020 Taler Systems SA
+ 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
@@ -82,9 +82,9 @@ handle_revoke_denomination_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementRevokeDenominationResponse rdr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
rh->job = NULL;
@@ -92,30 +92,30 @@ handle_revoke_denomination_finished (void *cls,
{
case 0:
/* no reply */
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = "server offline?";
+ rdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rdr.hr.hint = "server offline?";
break;
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) rdr.hr.ec);
break;
}
if (NULL != rh->cb)
{
rh->cb (rh->cb_cls,
- &hr);
+ &rdr);
rh->cb = NULL;
}
TALER_EXCHANGE_management_revoke_denomination_key_cancel (rh);
@@ -126,7 +126,7 @@ struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *
TALER_EXCHANGE_management_revoke_denomination_key (
struct GNUNET_CURL_Context *ctx,
const char *url,
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_MasterSignatureP *master_sig,
TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback cb,
void *cb_cls)
@@ -175,15 +175,18 @@ TALER_EXCHANGE_management_revoke_denomination_key (
return NULL;
}
eh = TALER_EXCHANGE_curl_easy_get_ (rh->url);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&rh->post_ctx,
- eh,
- body))
+ 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 (eh);
+ GNUNET_free (rh);
return NULL;
}
json_decref (body);
diff --git a/src/lib/exchange_api_management_revoke_signing_key.c b/src/lib/exchange_api_management_revoke_signing_key.c
index 046d18729..d2fa78264 100644
--- a/src/lib/exchange_api_management_revoke_signing_key.c
+++ b/src/lib/exchange_api_management_revoke_signing_key.c
@@ -79,9 +79,9 @@ handle_revoke_signing_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse rsr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
rh->job = NULL;
@@ -89,30 +89,30 @@ handle_revoke_signing_finished (void *cls,
{
case 0:
/* no reply */
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = "server offline?";
+ rsr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rsr.hr.hint = "server offline?";
break;
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) rsr.hr.ec);
break;
}
if (NULL != rh->cb)
{
rh->cb (rh->cb_cls,
- &hr);
+ &rsr);
rh->cb = NULL;
}
TALER_EXCHANGE_management_revoke_signing_key_cancel (rh);
@@ -165,16 +165,18 @@ TALER_EXCHANGE_management_revoke_signing_key (
GNUNET_JSON_pack_data_auto ("master_sig",
master_sig));
eh = TALER_EXCHANGE_curl_easy_get_ (rh->url);
- GNUNET_assert (NULL != eh);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&rh->post_ctx,
- eh,
- body))
+ 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 (eh);
+ GNUNET_free (rh);
return NULL;
}
json_decref (body);
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
index 807c6723a..aaeae21f4 100644
--- a/src/lib/exchange_api_management_set_wire_fee.c
+++ b/src/lib/exchange_api_management_set_wire_fee.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020-2021 Taler Systems SA
+ 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
@@ -79,9 +79,9 @@ handle_set_wire_fee_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementSetWireFeeResponse swr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
swfh->job = NULL;
@@ -90,32 +90,47 @@ handle_set_wire_fee_finished (void *cls,
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ swr.hr.ec = TALER_JSON_get_error_code (json);
+ swr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_PRECONDITION_FAILED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) swr.hr.ec);
break;
}
if (NULL != swfh->cb)
{
swfh->cb (swfh->cb_cls,
- &hr);
+ &swr);
swfh->cb = NULL;
}
TALER_EXCHANGE_management_set_wire_fees_cancel (swfh);
@@ -129,10 +144,9 @@ TALER_EXCHANGE_management_set_wire_fees (
const char *wire_method,
struct GNUNET_TIME_Timestamp validity_start,
struct GNUNET_TIME_Timestamp validity_end,
- const struct TALER_Amount *wire_fee,
- const struct TALER_Amount *closing_fee,
+ const struct TALER_WireFeeSet *fees,
const struct TALER_MasterSignatureP *master_sig,
- TALER_EXCHANGE_ManagementWireEnableCallback cb,
+ TALER_EXCHANGE_ManagementSetWireFeeCallback cb,
void *cb_cls)
{
struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh;
@@ -163,20 +177,22 @@ TALER_EXCHANGE_management_set_wire_fees (
GNUNET_JSON_pack_timestamp ("fee_end",
validity_end),
TALER_JSON_pack_amount ("closing_fee",
- closing_fee),
+ &fees->closing),
TALER_JSON_pack_amount ("wire_fee",
- wire_fee));
+ &fees->wire));
eh = TALER_EXCHANGE_curl_easy_get_ (swfh->url);
- GNUNET_assert (NULL != eh);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&swfh->post_ctx,
- eh,
- body))
+ 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 (eh);
+ GNUNET_free (swfh);
return NULL;
}
json_decref (body);
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
index 3bae8e3ab..23b10c58c 100644
--- a/src/lib/exchange_api_management_wire_disable.c
+++ b/src/lib/exchange_api_management_wire_disable.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2021 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
@@ -79,9 +79,9 @@ handle_auditor_disable_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementWireDisableHandle *wh = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementWireDisableResponse wdr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
wh->job = NULL;
@@ -89,38 +89,49 @@ handle_auditor_disable_finished (void *cls,
{
case 0:
/* no reply */
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = "server offline?";
+ wdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ wdr.hr.hint = "server offline?";
break;
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ wdr.hr.ec = TALER_JSON_get_error_code (json);
+ wdr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) wdr.hr.ec);
break;
}
if (NULL != wh->cb)
{
wh->cb (wh->cb_cls,
- &hr);
+ &wdr);
wh->cb = NULL;
}
TALER_EXCHANGE_management_disable_wire_cancel (wh);
@@ -163,16 +174,18 @@ TALER_EXCHANGE_management_disable_wire (
GNUNET_JSON_pack_timestamp ("validity_end",
validity_end));
eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
- GNUNET_assert (NULL != eh);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&wh->post_ctx,
- eh,
- body))
+ 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 (eh);
+ GNUNET_free (wh);
return NULL;
}
json_decref (body);
diff --git a/src/lib/exchange_api_management_wire_enable.c b/src/lib/exchange_api_management_wire_enable.c
index c4d5b13b4..9a163b558 100644
--- a/src/lib/exchange_api_management_wire_enable.c
+++ b/src/lib/exchange_api_management_wire_enable.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2021 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
@@ -79,9 +79,9 @@ handle_auditor_enable_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementWireEnableHandle *wh = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementWireEnableResponse wer = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
wh->job = NULL;
@@ -89,34 +89,49 @@ handle_auditor_enable_finished (void *cls,
{
case 0:
/* no reply */
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = "server offline?";
+ wer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ wer.hr.hint = "server offline?";
break;
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) wer.hr.ec);
break;
}
if (NULL != wh->cb)
{
wh->cb (wh->cb_cls,
- &hr);
+ &wer);
wh->cb = NULL;
}
TALER_EXCHANGE_management_enable_wire_cancel (wh);
@@ -128,9 +143,14 @@ 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)
{
@@ -138,6 +158,18 @@ TALER_EXCHANGE_management_enable_wire (
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;
@@ -155,6 +187,18 @@ TALER_EXCHANGE_management_enable_wire (
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",
@@ -162,16 +206,18 @@ TALER_EXCHANGE_management_enable_wire (
GNUNET_JSON_pack_timestamp ("validity_start",
validity_start));
eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
- GNUNET_assert (NULL != eh);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&wh->post_ctx,
- eh,
- body))
+ 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 (eh);
+ GNUNET_free (wh);
return NULL;
}
json_decref (body);
diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c
index f375171b9..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-2021 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,7 +84,28 @@ 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.
@@ -81,9 +113,24 @@ struct TALER_EXCHANGE_MeltHandle
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;
};
@@ -91,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 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,
@@ -121,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))
@@ -133,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,
- &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;
@@ -159,143 +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 denomination key differences
- * resulting from coin private key reuse 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 enum GNUNET_GenericReturnValue
-verify_melt_signature_denom_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
- const json_t *json)
-
-{
- json_t *history;
- struct TALER_Amount total;
- struct TALER_DenominationHash h_denom_pub;
-
- memset (&h_denom_pub,
- 0,
- sizeof (h_denom_pub));
- history = json_object_get (json,
- "history");
- if (GNUNET_OK !=
- TALER_EXCHANGE_verify_coin_history (&mh->dki,
- mh->dki.value.currency,
- &mh->coin_pub,
- history,
- &h_denom_pub,
- &total))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 != GNUNET_memcmp (&mh->dki.h_key,
- &h_denom_pub))
- return GNUNET_OK; /* indeed, proof with different denomination key provided */
- /* invalid proof provided */
- return GNUNET_SYSERR;
-}
-
-
-/**
- * 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 enum GNUNET_GenericReturnValue
-verify_melt_signature_spend_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
- const json_t *json)
-{
- json_t *history;
- struct TALER_Amount total;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("history",
- &history),
- GNUNET_JSON_spec_end ()
- };
- const struct MeltedCoin *mc;
- enum TALER_ErrorCode ec;
- struct TALER_DenominationHash h_denom_pub;
-
- /* 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;
- /* verify coin history */
- memset (&h_denom_pub,
- 0,
- sizeof (h_denom_pub));
- history = json_object_get (json,
- "history");
- if (GNUNET_OK !=
- TALER_EXCHANGE_verify_coin_history (&mh->dki,
- mc->original_value.currency,
- &mh->coin_pub,
- history,
- &h_denom_pub,
- &total))
- {
- GNUNET_break_op (0);
- json_decref (history);
- return GNUNET_SYSERR;
- }
- json_decref (history);
-
- ec = TALER_JSON_get_error_code (json);
- switch (ec)
- {
- case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
- /* check if melt operation was really too expensive given history */
- if (0 >
- TALER_amount_add (&total,
- &total,
- &mc->melt_amount_with_fee))
- {
- /* clearly not OK if our transaction would have caused
- the overflow... */
- return GNUNET_OK;
- }
-
- if (0 >= TALER_amount_cmp (&total,
- &mc->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;
- case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
- if (0 != GNUNET_memcmp (&mh->dki.h_key,
- &h_denom_pub))
- return GNUNET_OK; /* indeed, proof with different denomination key provided */
- /* invalid proof provided */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- default:
- /* unexpected error code */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-}
-
-
-/**
* Function called when we're done processing the
* HTTP /coins/$COIN_PUB/melt request.
*
@@ -309,209 +213,181 @@ 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;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ struct TALER_EXCHANGE_MeltResponse mr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
};
mh->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_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);
- hr.http_status = 0;
- hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
- }
- if (NULL != mh->melt_cb)
- {
- mh->melt_cb (mh->melt_cb_cls,
- &hr,
- noreveal_index,
- (0 == hr.http_status)
- ? NULL
- : &exchange_pub);
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_CONFLICT:
- hr.ec = TALER_JSON_get_error_code (j);
- switch (hr.ec)
- {
- case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
- /* Double spending; check signatures on transaction history */
- if (GNUNET_OK !=
- verify_melt_signature_spend_conflict (mh,
- j))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
- hr.hint = TALER_JSON_get_error_hint (j);
- }
- break;
- case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
- if (GNUNET_OK !=
- verify_melt_signature_denom_conflict (mh,
- j))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
- hr.hint = TALER_JSON_get_error_hint (j);
- }
- break;
- default:
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
- hr.hint = TALER_JSON_get_error_hint (j);
- break;
- }
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 for exchange melt\n",
(unsigned int) response_code,
- hr.ec);
+ mr.hr.ec);
GNUNET_break_op (0);
break;
}
if (NULL != mh->melt_cb)
mh->melt_cb (mh->melt_cb_cls,
- &hr,
- UINT32_MAX,
- NULL);
+ &mr);
TALER_EXCHANGE_melt_cancel (mh);
}
-struct TALER_EXCHANGE_MeltHandle *
-TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
- const json_t *refresh_data,
- TALER_EXCHANGE_MeltCallback melt_cb,
- void *melt_cb_cls)
+/**
+ * Start the actual melt operation, now that we have
+ * the exchange's input values.
+ *
+ * @param[in,out] mh melt operation to run
+ * @return #GNUNET_OK if we could start the operation
+ */
+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;
char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
- struct TALER_DenominationHash h_denom_pub;
- struct TALER_CoinSpendPublicKeyP coin_pub;
+ 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,
- exchange->key_data.currency);
- if (NULL == md)
+ for (unsigned int i = 0; i<mh->rd->fresh_pks_len; i++)
+ {
+ 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;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_get_melt_data_ (&mh->rms,
+ mh->rd,
+ alg_values,
+ &mh->md))
{
GNUNET_break (0);
- return NULL;
+ return GNUNET_SYSERR;
}
- TALER_denom_pub_hash (&md->melted_coin.pub_key,
+ TALER_denom_pub_hash (&mh->md.melted_coin.pub_key,
&h_denom_pub);
- TALER_wallet_melt_sign (&md->melted_coin.melt_amount_with_fee,
- &md->melted_coin.fee_melt,
- &md->rc,
- &h_denom_pub,
- &md->melted_coin.coin_priv,
- &confirm_sig);
- GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv,
- &coin_pub.eddsa_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",
- &md->melted_coin.sig),
+ &mh->md.melted_coin.sig),
GNUNET_JSON_pack_data_auto ("confirm_sig",
- &confirm_sig),
+ &mh->coin_sig),
TALER_JSON_pack_amount ("value_with_fee",
- &md->melted_coin.melt_amount_with_fee),
+ &mh->md.melted_coin.melt_amount_with_fee),
GNUNET_JSON_pack_data_auto ("rc",
- &md->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 (
- &coin_pub,
+ &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->coin_pub = coin_pub;
- mh->dki = *dki;
- memset (&mh->dki.key,
- 0,
- sizeof (mh->dki.key)); /* lifetime not warranted, so better
- not copy the pointers */
- 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);
- GNUNET_free (mh);
- return NULL;
+ return GNUNET_SYSERR;
}
eh = TALER_EXCHANGE_curl_easy_get_ (mh->url);
if ( (NULL == eh) ||
@@ -524,17 +400,176 @@ 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 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);
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * CS R request to a exchange.
+ *
+ * @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;
}
@@ -542,15 +577,24 @@ TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
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 10f74ce65..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-2021 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;
@@ -94,16 +100,15 @@ static enum GNUNET_GenericReturnValue
process_recoup_response (const struct TALER_EXCHANGE_RecoupHandle *ph,
const json_t *json)
{
- struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_EXCHANGE_RecoupResponse rr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
struct GNUNET_JSON_Specification spec_withdraw[] = {
GNUNET_JSON_spec_fixed_auto ("reserve_pub",
- &reserve_pub),
+ &rr.details.ok.reserve_pub),
GNUNET_JSON_spec_end ()
};
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = json,
- .http_status = MHD_HTTP_OK
- };
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
@@ -114,8 +119,7 @@ process_recoup_response (const struct TALER_EXCHANGE_RecoupHandle *ph,
return GNUNET_SYSERR;
}
ph->cb (ph->cb_cls,
- &hr,
- &reserve_pub);
+ &rr);
return GNUNET_OK;
}
@@ -135,16 +139,16 @@ handle_recoup_finished (void *cls,
{
struct TALER_EXCHANGE_RecoupHandle *ph = cls;
const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ struct TALER_EXCHANGE_RecoupResponse rr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
};
ph->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
@@ -152,8 +156,8 @@ handle_recoup_finished (void *cls,
j))
{
GNUNET_break_op (0);
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 0;
+ rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ rr.hr.http_status = 0;
break;
}
TALER_EXCHANGE_recoup_cancel (ph);
@@ -161,189 +165,167 @@ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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;
- struct TALER_DenominationHash h_denom_pub;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
- enum TALER_ErrorCode ec;
-
- 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,
- &h_denom_pub,
- &total))
+ TALER_EXCHANGE_get_min_denomination_ (ph->keys,
+ &min_key))
{
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- }
- else
- {
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- }
- ec = TALER_JSON_get_error_code (j);
- switch (ec)
- {
- case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
- if (0 > TALER_amount_cmp (&total,
- &dki->value))
- {
- /* recoup MAY have still been possible */
- /* FIXME: This code may falsely complain, as we do not
- know that the smallest denomination offered by the
- exchange is here. We should look at the key
- structure of ph->exchange, and find the smallest
- _currently withdrawable_ denomination and check
- if the value remaining would suffice... *///
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- break;
- case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
- if (0 == GNUNET_memcmp (&ph->pk.h_key,
- &h_denom_pub))
- {
- /* invalid proof provided */
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- /* valid error from exchange */
- break;
- default:
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ GNUNET_break (0);
+ rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ rr.hr.http_status = 0;
break;
}
- ph->cb (ph->cb_cls,
- &hr,
- NULL);
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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). */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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/%d for exchange recoup\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) rr.hr.ec);
GNUNET_break (0);
break;
}
ph->cb (ph->cb_cls,
- &hr,
- NULL);
+ &rr);
TALER_EXCHANGE_recoup_cancel (ph);
}
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,
- 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_CoinSpendSignatureP coin_sig;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_DenominationHash 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));
- GNUNET_CRYPTO_eddsa_key_get_public (&ps->coin_priv.eddsa_priv,
- &coin_pub.eddsa_pub);
+ 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,
- &ps->blinding_key,
- &ps->coin_priv,
- &coin_sig);
+ &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",
- &coin_sig),
+ &ph->coin_sig),
GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
- &ps->blinding_key));
+ &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 (
- &coin_pub,
+ &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 = coin_pub;
- ph->exchange = exchange;
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;
- ph->url = TEAH_path_to_url (exchange,
- arg_str);
+ ph->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == ph->url)
{
json_decref (recoup_obj);
@@ -369,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,
@@ -389,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
index 03f756646..0c2e21cbf 100644
--- a/src/lib/exchange_api_recoup_refresh.c
+++ b/src/lib/exchange_api_recoup_refresh.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2017-2021 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_RecoupRefreshHandle
{
/**
- * 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,6 +80,11 @@ struct TALER_EXCHANGE_RecoupRefreshHandle
*/
struct TALER_CoinSpendPublicKeyP coin_pub;
+ /**
+ * Signature affirming the recoup-refresh operation.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
};
@@ -95,16 +101,15 @@ process_recoup_response (
const struct TALER_EXCHANGE_RecoupRefreshHandle *ph,
const json_t *json)
{
- struct TALER_CoinSpendPublicKeyP old_coin_pub;
+ 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",
- &old_coin_pub),
+ &rrr.details.ok.old_coin_pub),
GNUNET_JSON_spec_end ()
};
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = json,
- .http_status = MHD_HTTP_OK
- };
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
@@ -115,8 +120,7 @@ process_recoup_response (
return GNUNET_SYSERR;
}
ph->cb (ph->cb_cls,
- &hr,
- &old_coin_pub);
+ &rrr);
return GNUNET_OK;
}
@@ -136,16 +140,16 @@ handle_recoup_refresh_finished (void *cls,
{
struct TALER_EXCHANGE_RecoupRefreshHandle *ph = cls;
const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ struct TALER_EXCHANGE_RecoupRefreshResponse rrr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
};
ph->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
@@ -153,8 +157,8 @@ handle_recoup_refresh_finished (void *cls,
j))
{
GNUNET_break_op (0);
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 0;
+ rrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ rrr.hr.http_status = 0;
break;
}
TALER_EXCHANGE_recoup_refresh_cancel (ph);
@@ -162,190 +166,160 @@ handle_recoup_refresh_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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
break;
- case MHD_HTTP_CONFLICT:
- {
- /* Insufficient funds, proof attached */
- json_t *history;
- struct TALER_Amount total;
- struct TALER_DenominationHash h_denom_pub;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
- enum TALER_ErrorCode ec;
-
- dki = &ph->pk;
- history = json_object_get (j,
- "history");
- if (GNUNET_OK !=
- TALER_EXCHANGE_verify_coin_history (dki,
- dki->fee_deposit.currency,
- &ph->coin_pub,
- history,
- &h_denom_pub,
- &total))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- }
- else
- {
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- }
- ec = TALER_JSON_get_error_code (j);
- switch (ec)
- {
- case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
- if (0 > TALER_amount_cmp (&total,
- &dki->value))
- {
- /* recoup MAY have still been possible */
- /* FIXME: This code may falsely complain, as we do not
- know that the smallest denomination offered by the
- exchange is here. We should look at the key
- structure of ph->exchange, and find the smallest
- _currently withdrawable_ denomination and check
- if the value remaining would suffice... */
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- break;
- case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
- if (0 == GNUNET_memcmp (&ph->pk.h_key,
- &h_denom_pub))
- {
- /* invalid proof provided */
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- /* valid error from exchange */
- break;
- default:
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- ph->cb (ph->cb_cls,
- &hr,
- NULL);
- TALER_EXCHANGE_recoup_refresh_cancel (ph);
- return;
- }
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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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). */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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) hr.ec);
+ (int) rrr.hr.ec);
GNUNET_break (0);
break;
}
ph->cb (ph->cb_cls,
- &hr,
- NULL);
+ &rrr);
TALER_EXCHANGE_recoup_refresh_cancel (ph);
}
struct TALER_EXCHANGE_RecoupRefreshHandle *
TALER_EXCHANGE_recoup_refresh (
- struct TALER_EXCHANGE_Handle *exchange,
+ 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_PlanchetSecretsP *ps,
+ 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 GNUNET_CURL_Context *ctx;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_DenominationHash 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));
- GNUNET_CRYPTO_eddsa_key_get_public (&ps->coin_priv.eddsa_priv,
- &coin_pub.eddsa_pub);
+ 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,
- &ps->blinding_key,
- &ps->coin_priv,
- &coin_sig);
+ &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",
- &coin_sig),
+ &ph->coin_sig),
GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
- &ps->blinding_key));
+ &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 (
- &coin_pub,
+ &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",
+ "coins/%s/recoup-refresh",
pub_str);
}
- ph = GNUNET_new (struct TALER_EXCHANGE_RecoupRefreshHandle);
- ph->coin_pub = coin_pub;
- ph->exchange = exchange;
- 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;
- ph->url = TEAH_path_to_url (exchange,
- arg_str);
+ ph->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == ph->url)
{
json_decref (recoup_obj);
@@ -371,7 +345,7 @@ TALER_EXCHANGE_recoup_refresh (
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"URL for recoup-refresh: `%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,
@@ -392,6 +366,7 @@ TALER_EXCHANGE_recoup_refresh_cancel (
}
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_refresh_common.c b/src/lib/exchange_api_refresh_common.c
index 3e367566d..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-2021 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,444 +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)
-{
- TALER_denom_pub_free (&mc->pub_key);
- TALER_denom_sig_free (&mc->sig);
-}
-
-
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++)
- TALER_denom_pub_free (&md->fresh_pks[i]);
- GNUNET_free (md->fresh_pks);
- }
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- GNUNET_free (md->fresh_coins[i]);
- /* Finally, clean up a bit... */
- GNUNET_CRYPTO_zero_keys (md,
- sizeof (struct MeltData));
-}
-
-
-/**
- * Serialize information about a coin we are melting.
- *
- * @param mc information to serialize
- * @return NULL on error
- */
-static json_t *
-serialize_melted_coin (const struct MeltedCoin *mc)
-{
- json_t *tprivs;
-
- tprivs = json_array ();
- GNUNET_assert (NULL != tprivs);
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- GNUNET_assert (0 ==
- json_array_append_new (
- tprivs,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto (
- "transfer_priv",
- &mc->transfer_priv[i]))));
- return GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("coin_priv",
- &mc->coin_priv),
- TALER_JSON_pack_denom_sig ("denom_sig",
- &mc->sig),
- TALER_JSON_pack_denom_pub ("denom_pub",
- &mc->pub_key),
- TALER_JSON_pack_amount ("melt_amount_with_fee",
- &mc->melt_amount_with_fee),
- TALER_JSON_pack_amount ("original_value",
- &mc->original_value),
- TALER_JSON_pack_amount ("melt_fee",
- &mc->fee_melt),
- GNUNET_JSON_pack_timestamp ("expire_deposit",
- mc->expire_deposit),
- GNUNET_JSON_pack_array_steal ("transfer_privs",
- tprivs));
-}
-
-
-/**
- * Deserialize information about a coin we are melting.
- *
- * @param[out] mc information to deserialize
- * @param currency expected currency
- * @param in JSON object to read data from
- * @return #GNUNET_NO to report errors
- */
-static enum GNUNET_GenericReturnValue
-deserialize_melted_coin (struct MeltedCoin *mc,
- const char *currency,
- const json_t *in)
-{
- json_t *trans_privs;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_priv",
- &mc->coin_priv),
- TALER_JSON_spec_denom_sig ("denom_sig",
- &mc->sig),
- TALER_JSON_spec_denom_pub ("denom_pub",
- &mc->pub_key),
- TALER_JSON_spec_amount ("melt_amount_with_fee",
- currency,
- &mc->melt_amount_with_fee),
- TALER_JSON_spec_amount ("original_value",
- currency,
- &mc->original_value),
- TALER_JSON_spec_amount ("melt_fee",
- currency,
- &mc->fee_melt),
- GNUNET_JSON_spec_timestamp ("expire_deposit",
- &mc->expire_deposit),
- GNUNET_JSON_spec_json ("transfer_privs",
- &trans_privs),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (in,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_NO;
- }
- if (TALER_CNC_KAPPA != json_array_size (trans_privs))
- {
- GNUNET_JSON_parse_free (spec);
- GNUNET_break_op (0);
- return GNUNET_NO;
- }
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("transfer_priv",
- &mc->transfer_priv[i]),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json_array_get (trans_privs,
- i),
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_NO;
- }
- }
- json_decref (trans_privs);
- return GNUNET_OK;
-}
-
-
-/**
- * Serialize melt data.
- *
- * @param md data to serialize
- * @return serialized melt data
- */
-static json_t *
-serialize_melt_data (const struct MeltData *md)
-{
- json_t *fresh_coins;
-
- fresh_coins = json_array ();
- GNUNET_assert (NULL != fresh_coins);
- for (int i = 0; i<md->num_fresh_coins; i++)
+ for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++)
{
- json_t *planchet_secrets;
-
- planchet_secrets = json_array ();
- GNUNET_assert (NULL != planchet_secrets);
- for (unsigned int j = 0; j<TALER_CNC_KAPPA; j++)
- {
- json_t *ps;
-
- ps = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("ps",
- &md->fresh_coins[j][i]));
- GNUNET_assert (0 ==
- json_array_append_new (planchet_secrets,
- ps));
- }
- GNUNET_assert (0 ==
- json_array_append_new (
- fresh_coins,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_denom_pub ("denom_pub",
- &md->fresh_pks[i]),
- GNUNET_JSON_pack_array_steal ("planchet_secrets",
- planchet_secrets)))
- );
- }
- return GNUNET_JSON_PACK (
- GNUNET_JSON_pack_array_steal ("fresh_coins",
- fresh_coins),
- GNUNET_JSON_pack_object_steal ("melted_coin",
- serialize_melted_coin (&md->melted_coin)),
- GNUNET_JSON_pack_data_auto ("rc",
- &md->rc));
-}
-
+ struct TALER_RefreshCoinData *rcds = md->rcd[i];
-struct MeltData *
-TALER_EXCHANGE_deserialize_melt_data_ (const json_t *melt_data,
- const char *currency)
-{
- struct MeltData *md = GNUNET_new (struct MeltData);
- json_t *fresh_coins;
- json_t *melted_coin;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("rc",
- &md->rc),
- GNUNET_JSON_spec_json ("melted_coin",
- &melted_coin),
- GNUNET_JSON_spec_json ("fresh_coins",
- &fresh_coins),
- GNUNET_JSON_spec_end ()
- };
- bool ok;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (melt_data,
- spec,
- NULL, NULL))
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- GNUNET_free (md);
- return NULL;
- }
- if (! (json_is_array (fresh_coins) &&
- json_is_object (melted_coin)) )
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return NULL;
- }
- if (GNUNET_OK !=
- deserialize_melted_coin (&md->melted_coin,
- currency,
- melted_coin))
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return NULL;
+ 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);
}
- md->num_fresh_coins = json_array_size (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);
- ok = true;
- for (unsigned int i = 0; i<md->num_fresh_coins; i++)
+ TALER_denom_pub_free (&md->melted_coin.pub_key);
+ TALER_denom_sig_free (&md->melted_coin.sig);
+ if (NULL != md->fcds)
{
- const json_t *ji = json_array_get (fresh_coins,
- i);
- json_t *planchet_secrets;
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_json ("planchet_secrets",
- &planchet_secrets),
- TALER_JSON_spec_denom_pub ("denom_pub",
- &md->fresh_pks[i]),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (ji,
- ispec,
- NULL, NULL))
- {
- GNUNET_break (0);
- ok = false;
- break;
- }
- if ( (! json_is_array (planchet_secrets)) ||
- (TALER_CNC_KAPPA != json_array_size (planchet_secrets)) )
- {
- GNUNET_break (0);
- ok = false;
- GNUNET_JSON_parse_free (ispec);
- break;
- }
- for (unsigned int j = 0; j<TALER_CNC_KAPPA; j++)
+ for (unsigned int j = 0; j<md->num_fresh_coins; j++)
{
- struct GNUNET_JSON_Specification jspec[] = {
- GNUNET_JSON_spec_fixed_auto ("ps",
- &md->fresh_coins[j][i]),
- GNUNET_JSON_spec_end ()
- };
+ struct FreshCoinData *fcd = &md->fcds[j];
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json_array_get (planchet_secrets,
- j),
- jspec,
- NULL, NULL))
+ TALER_denom_pub_free (&fcd->fresh_pk);
+ for (size_t i = 0; i < TALER_CNC_KAPPA; i++)
{
- GNUNET_break (0);
- ok = false;
- break;
+ TALER_age_commitment_proof_free (fcd->age_commitment_proofs[i]);
+ GNUNET_free (fcd->age_commitment_proofs[i]);
}
}
- json_decref (planchet_secrets);
- if (! ok)
- break;
- }
-
- GNUNET_JSON_parse_free (spec);
- if (! ok)
- {
- TALER_EXCHANGE_free_melt_data_ (md);
- GNUNET_free (md);
- return NULL;
+ GNUNET_free (md->fcds);
}
- return md;
+ /* Finally, clean up a bit... */
+ GNUNET_CRYPTO_zero_keys (md,
+ sizeof (struct MeltData));
}
-json_t *
-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)
+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;
- json_t *ret;
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,
+ 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;
+ 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_set_zero (melt_amount->currency,
+ TALER_amount_set_zero (rd->melt_amount.currency,
&total));
- TALER_denom_pub_deep_copy (&md.melted_coin.pub_key,
- &melt_pk->key);
- TALER_denom_sig_deep_copy (&md.melted_coin.sig,
- melt_sig);
- 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++)
{
- TALER_denom_pub_deep_copy (&md.fresh_pks[i],
- &fresh_pks[i].key);
+ 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)) ||
+ &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++)
{
- GNUNET_CRYPTO_ecdhe_key_create (
- &md.melted_coin.transfer_priv[i].ecdhe_priv);
+ struct TALER_TransferSecretP trans_sec;
+
+ 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_CoinPubHash c_hash;
+ 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 */
- ret = serialize_melt_data (&md);
- 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 (rce[i].new_coins[j].coin_ev);
- GNUNET_free (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 ret;
+ return GNUNET_OK;
}
diff --git a/src/lib/exchange_api_refresh_common.h b/src/lib/exchange_api_refresh_common.h
index 1c037d966..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
@@ -53,9 +53,11 @@ struct MeltedCoin
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.
@@ -76,6 +78,43 @@ struct MeltedCoin
/**
+ * Data we keep for each fresh coin created in the
+ * melt process.
+ */
+struct FreshCoinData
+{
+ /**
+ * Denomination public key of the coin.
+ */
+ struct TALER_DenominationPublicKey fresh_pk;
+
+ /**
+ * Array of planchet secrets for the coins, depending
+ * on the cut-and-choose.
+ */
+ struct TALER_PlanchetMasterSecretP ps[TALER_CNC_KAPPA];
+
+ /**
+ * Private key of the coin.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * 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];
+
+};
+
+
+/**
* Melt data in non-serialized format for convenient processing.
*/
struct MeltData
@@ -87,46 +126,71 @@ struct MeltData
struct TALER_RefreshCommitmentP rc;
/**
- * Number of coins we are creating
+ * Information about the melted coin.
*/
- uint16_t num_fresh_coins;
+ struct MeltedCoin melted_coin;
/**
- * Information about the melted coin.
+ * Array of length @e num_fresh_coins with information
+ * about each fresh coin.
*/
- struct MeltedCoin melted_coin;
+ struct FreshCoinData *fcds;
+
+ /**
+ * Transfer secrets, one per cut and choose.
+ */
+ struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA];
+
+ /**
+ * Transfer private keys for each cut-and-choose dimension.
+ */
+ struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA];
+
+ /**
+ * Transfer public key of this commitment.
+ */
+ struct TALER_TransferPublicKeyP transfer_pub[TALER_CNC_KAPPA];
/**
- * Array of @e num_fresh_coins denomination keys for the coins to be
- * freshly exchangeed.
+ * Transfer secrets, one per cut and choose.
*/
- struct TALER_DenominationPublicKey *fresh_pks;
+ struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA];
/**
- * Arrays of @e num_fresh_coins with information about the fresh
- * coins to be created, for each cut-and-choose dimension.
+ * Blinded planchets and denominations of the fresh coins, depending on the cut-and-choose. Array of length
+ * @e num_fresh_coins.
*/
- struct TALER_PlanchetSecretsP *fresh_coins[TALER_CNC_KAPPA];
+ struct TALER_RefreshCoinData *rcd[TALER_CNC_KAPPA];
+
+ /**
+ * Number of coins we are creating
+ */
+ uint16_t num_fresh_coins;
+
};
/**
- * Deserialize melt data.
+ * Compute the melt data from the refresh data and secret.
*
- * @param data json data to deserialize
- * @param currency expected currency for the coins
- * @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 json_t *data,
- const char *currency);
+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[in] md melting data to release, the pointer itself is NOT
* freed (as it is typically not allocated by itself)
diff --git a/src/lib/exchange_api_refreshes_reveal.c b/src/lib/exchange_api_refreshes_reveal.c
index 2b7fcf8cf..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-2021 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,29 +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 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 ()
};
@@ -118,39 +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 TALER_BlindedDenominationSignature blind_sig;
struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_CoinPubHash coin_hash;
+ struct TALER_CoinPubHashP coin_hash;
struct GNUNET_JSON_Specification spec[] = {
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,
@@ -158,34 +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);
- /* FIXME-Oec: Age commitment hash. */
- TALER_coin_pub_hash (&coin_pub,
- NULL, /* FIXME-Oec */
- &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_JSON_parse_free (spec);
- GNUNET_JSON_parse_free (outer_spec);
return GNUNET_SYSERR;
}
GNUNET_JSON_parse_free (spec);
- sigs[i] = coin.sig;
+ rci->sig = coin.sig;
}
- GNUNET_JSON_parse_free (outer_spec);
return GNUNET_OK;
}
@@ -205,96 +216,101 @@ handle_refresh_reveal_finished (void *cls,
{
struct TALER_EXCHANGE_RefreshesRevealHandle *rrh = cls;
const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ struct TALER_EXCHANGE_RevealResult rr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
};
rrh->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_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,
+ memset (rcis,
0,
- sizeof (sigs));
+ sizeof (rcis));
ret = refresh_reveal_ok (rrh,
j,
- sigs);
+ rcis);
if (GNUNET_OK != ret)
{
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ rr.hr.http_status = 0;
+ rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
}
else
{
+ 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,
- &hr,
- rrh->md->num_fresh_coins,
- rrh->md->fresh_coins[rrh->noreveal_index],
- sigs);
+ &rr);
rrh->reveal_cb = NULL;
}
- for (unsigned int i = 0; i<rrh->md->num_fresh_coins; i++)
- TALER_denom_sig_free (&sigs[i]);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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);
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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/%d for exchange refreshes reveal\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) rr.hr.ec);
break;
}
if (NULL != rrh->reveal_cb)
rrh->reveal_cb (rrh->reveal_cb_cls,
- &hr,
- 0,
- NULL,
- NULL);
+ &rr);
TALER_EXCHANGE_refreshes_reveal_cancel (rrh);
}
struct TALER_EXCHANGE_RefreshesRevealHandle *
TALER_EXCHANGE_refreshes_reveal (
- struct TALER_EXCHANGE_Handle *exchange,
- const json_t *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)
@@ -305,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
@@ -320,73 +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,
- exchange->key_data.currency);
- 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 TALER_DenominationHash denom_hash;
- struct TALER_PlanchetDetail pd;
- struct TALER_CoinPubHash c_hash;
+ const struct TALER_RefreshCoinData *rcd = &md.rcd[noreveal_index][i];
+ struct TALER_DenominationHashP denom_hash;
- TALER_denom_pub_hash (&md->fresh_pks[i],
+ 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],
- &c_hash,
- &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)));
+ json_array_append_new (
+ coin_evs,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_planchet (
+ NULL,
+ &rcd->blinded_planchet))));
{
struct TALER_CoinSpendSignatureP link_sig;
-
- TALER_wallet_link_sign (&denom_hash,
- &transfer_pub,
- pd.coin_ev,
- pd.coin_ev_size,
- &md->melted_coin.coin_priv,
- &link_sig);
+ 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)));
}
- GNUNET_free (pd.coin_ev);
}
/* build array of transfer private keys */
@@ -395,20 +398,46 @@ 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 array of old age commitment, if applicable */
+ if (NULL != rd->melt_age_commitment_proof)
+ {
+ 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",
- &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",
@@ -421,32 +450,40 @@ TALER_EXCHANGE_refreshes_reveal (
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) ||
(GNUNET_OK !=
@@ -458,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,
@@ -482,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 94909470a..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-2021 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;
};
@@ -93,7 +117,6 @@ verify_refund_signature_ok (struct TALER_EXCHANGE_RefundHandle *rh,
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),
@@ -110,19 +133,22 @@ verify_refund_signature_ok (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,
- &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;
@@ -132,303 +158,6 @@ verify_refund_signature_ok (struct TALER_EXCHANGE_RefundHandle *rh,
/**
- * Verify that the information in the "409 Conflict" response
- * from the exchange is valid and indeed shows that the refund
- * amount requested is too high.
- *
- * @param[in,out] rh refund handle (refund fee added)
- * @param json json reply with the coin transaction history
- * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
- */
-static enum GNUNET_GenericReturnValue
-verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh,
- const json_t *json)
-{
- json_t *history;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("history",
- &history),
- GNUNET_JSON_spec_end ()
- };
- size_t len;
- struct TALER_Amount dtotal;
- bool have_deposit;
- struct TALER_Amount rtotal;
- bool have_refund;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- len = json_array_size (history);
- if (0 == len)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- have_deposit = false;
- have_refund = false;
- for (size_t off = 0; off<len; off++)
- {
- json_t *transaction;
- struct TALER_Amount amount;
- const char *type;
- struct GNUNET_JSON_Specification spec_glob[] = {
- TALER_JSON_spec_amount_any ("amount",
- &amount),
- GNUNET_JSON_spec_string ("type",
- &type),
- GNUNET_JSON_spec_end ()
- };
-
- transaction = json_array_get (history,
- off);
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec_glob,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (0 == strcasecmp (type,
- "DEPOSIT"))
- {
- struct TALER_Amount deposit_fee;
- struct TALER_MerchantWireHash h_wire;
- struct TALER_PrivateContractHash h_contract_terms;
- // struct TALER_ExtensionContractHash h_extensions; // FIXME!
- struct TALER_DenominationHash h_denom_pub;
- struct GNUNET_TIME_Timestamp wallet_timestamp;
- struct TALER_MerchantPublicKeyP merchant_pub;
- struct GNUNET_TIME_Timestamp refund_deadline;
- struct TALER_CoinSpendSignatureP sig;
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &sig),
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &h_contract_terms),
- GNUNET_JSON_spec_fixed_auto ("h_wire",
- &h_wire),
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- &h_denom_pub),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &wallet_timestamp),
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &refund_deadline),
- TALER_JSON_spec_amount_any ("deposit_fee",
- &deposit_fee),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &merchant_pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_deposit_verify (&amount,
- &deposit_fee,
- &h_wire,
- &h_contract_terms,
- NULL /* h_extensions! */,
- &h_denom_pub,
- wallet_timestamp,
- &merchant_pub,
- refund_deadline,
- &rh->depconf.coin_pub,
- &sig))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if ( (0 != GNUNET_memcmp (&rh->depconf.h_contract_terms,
- &h_contract_terms)) ||
- (0 != GNUNET_memcmp (&rh->depconf.merchant,
- &merchant_pub)) )
- {
- /* deposit information is about a different merchant/contract */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (have_deposit)
- {
- /* this cannot really happen, but we conservatively support it anyway */
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (&amount,
- &dtotal))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- GNUNET_break (0 <=
- TALER_amount_add (&dtotal,
- &dtotal,
- &amount));
- }
- else
- {
- dtotal = amount;
- have_deposit = true;
- }
- }
- else if (0 == strcasecmp (type,
- "REFUND"))
- {
- struct TALER_MerchantSignatureP sig;
- struct TALER_Amount refund_fee;
- struct TALER_Amount sig_amount;
- struct TALER_PrivateContractHash h_contract_terms;
- uint64_t rtransaction_id;
- struct TALER_MerchantPublicKeyP merchant_pub;
- struct GNUNET_JSON_Specification ispec[] = {
- 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 (transaction,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (0 >
- TALER_amount_add (&sig_amount,
- &refund_fee,
- &amount))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_merchant_refund_verify (&rh->depconf.coin_pub,
- &h_contract_terms,
- rtransaction_id,
- &sig_amount,
- &merchant_pub,
- &sig))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if ( (0 != GNUNET_memcmp (&rh->depconf.h_contract_terms,
- &h_contract_terms)) ||
- (0 != GNUNET_memcmp (&rh->depconf.merchant,
- &merchant_pub)) )
- {
- /* refund is about a different merchant/contract */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (rtransaction_id == rh->depconf.rtransaction_id)
- {
- /* Eh, this shows either a dependency failure or idempotency,
- but must not happen in a conflict reply. Fail! */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
-
- if (have_refund)
- {
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (&amount,
- &rtotal))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- GNUNET_break (0 <=
- TALER_amount_add (&rtotal,
- &rtotal,
- &amount));
- }
- else
- {
- rtotal = amount;
- have_refund = true;
- }
- }
- else
- {
- /* unexpected type, new version on server? */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected type `%s' in response for exchange refund\n",
- type);
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- }
-
- {
- struct TALER_Amount amount;
-
- TALER_amount_ntoh (&amount,
- &rh->depconf.refund_amount);
- if (have_refund)
- {
- if (0 >
- TALER_amount_add (&rtotal,
- &rtotal,
- &amount))
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- }
- else
- {
- rtotal = amount;
- }
- }
- if (-1 == TALER_amount_cmp (&dtotal,
- &rtotal))
- {
- /* dtotal < rtotal: good! */
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
- }
- /* this fails to prove a conflict */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
-}
-
-
-/**
* 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.
@@ -441,11 +170,11 @@ static enum GNUNET_GenericReturnValue
verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh,
const json_t *json)
{
- json_t *h;
+ const json_t *h;
json_t *e;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("history",
- &h),
+ GNUNET_JSON_spec_array_const ("history",
+ &h),
GNUNET_JSON_spec_end ()
};
@@ -457,21 +186,18 @@ verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if ( (! json_is_array (h)) ||
- (1 != json_array_size (h) ) )
+ if (1 != json_array_size (h))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
e = json_array_get (h, 0);
{
struct TALER_Amount amount;
- struct TALER_Amount depconf_amount;
const char *type;
struct TALER_MerchantSignatureP sig;
struct TALER_Amount refund_fee;
- struct TALER_PrivateContractHash h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
uint64_t rtransaction_id;
struct TALER_MerchantPublicKeyP merchant_pub;
struct GNUNET_JSON_Specification ispec[] = {
@@ -498,11 +224,10 @@ verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh,
NULL, NULL))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
- TALER_merchant_refund_verify (&rh->depconf.coin_pub,
+ TALER_merchant_refund_verify (&rh->coin_pub,
&h_contract_terms,
rtransaction_id,
&amount,
@@ -510,25 +235,20 @@ verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh,
&sig))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
- TALER_amount_ntoh (&depconf_amount,
- &rh->depconf.refund_amount);
- if ( (rtransaction_id != rh->depconf.rtransaction_id) ||
- (0 != GNUNET_memcmp (&rh->depconf.h_contract_terms,
+ if ( (rtransaction_id != rh->rtransaction_id) ||
+ (0 != GNUNET_memcmp (&rh->h_contract_terms,
&h_contract_terms)) ||
- (0 != GNUNET_memcmp (&rh->depconf.merchant,
+ (0 != GNUNET_memcmp (&rh->merchant,
&merchant_pub)) ||
- (0 == TALER_amount_cmp (&depconf_amount,
+ (0 == TALER_amount_cmp (&rh->refund_amount,
&amount)) )
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
}
- GNUNET_JSON_parse_free (spec);
return GNUNET_OK;
}
@@ -547,37 +267,28 @@ handle_refund_finished (void *cls,
const void *response)
{
struct TALER_EXCHANGE_RefundHandle *rh = cls;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangeSignatureP exchange_sig;
- struct TALER_ExchangePublicKeyP *ep = NULL;
- struct TALER_ExchangeSignatureP *es = NULL;
const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ struct TALER_EXCHANGE_RefundResponse rr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
};
rh->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_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,
- &exchange_sig))
+ &rr.details.ok.exchange_pub,
+ &rr.details.ok.exchange_sig))
{
GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE;
- }
- else
- {
- ep = &exchange_pub;
- es = &exchange_sig;
+ rr.hr.http_status = 0;
+ rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE;
}
break;
case MHD_HTTP_BAD_REQUEST:
@@ -585,42 +296,36 @@ handle_refund_finished (void *cls,
(or API version conflict); also can happen if the currency
differs (which we should obviously never support).
Just pass JSON reply to the application */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- if (GNUNET_OK !=
- verify_conflict_history_ok (rh,
- j))
- {
- GNUNET_break (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE;
- hr.hint = "conflict information provided by exchange is invalid";
- break;
- }
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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). */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ 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_PRECONDITION_FAILED:
if (GNUNET_OK !=
@@ -628,61 +333,59 @@ handle_refund_finished (void *cls,
j))
{
GNUNET_break (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE;
- hr.hint = "failed precondition proof returned by exchange is invalid";
+ 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. */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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);
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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/%d for exchange refund\n",
(unsigned int) response_code,
- hr.ec);
+ rr.hr.ec);
break;
}
rh->cb (rh->cb_cls,
- &hr,
- ep,
- es);
+ &rr);
TALER_EXCHANGE_refund_cancel (rh);
}
struct TALER_EXCHANGE_RefundHandle *
-TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_Amount *amount,
- const struct TALER_PrivateContractHash *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_MerchantPublicKeyP merchant_pub;
struct TALER_MerchantSignatureP merchant_sig;
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_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
&merchant_pub.eddsa_pub);
TALER_merchant_refund_sign (coin_pub,
@@ -703,7 +406,7 @@ TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange,
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/coins/%s/refund",
+ "coins/%s/refund",
pub_str);
}
refund_obj = GNUNET_JSON_PACK (
@@ -718,26 +421,22 @@ TALER_EXCHANGE_refund (struct TALER_EXCHANGE_Handle *exchange,
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->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == rh->url)
{
json_decref (refund_obj);
GNUNET_free (rh);
return NULL;
}
- 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);
-
+ 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 !=
@@ -757,7 +456,7 @@ TALER_EXCHANGE_refund (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,
@@ -777,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 7f919ef80..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-2021 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,22 +74,19 @@ 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_any ("balance", &balance),
+ TALER_JSON_spec_amount_any ("balance",
+ &rs.details.ok.balance),
GNUNET_JSON_spec_end ()
};
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = MHD_HTTP_OK
- };
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
@@ -105,55 +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,
- &hr,
- &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;
}
@@ -173,61 +119,60 @@ handle_reserves_get_finished (void *cls,
{
struct TALER_EXCHANGE_ReservesGetHandle *rgh = cls;
const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ struct TALER_EXCHANGE_ReserveSummary rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
};
rgh->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
handle_reserves_get_ok (rgh,
j))
{
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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);
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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\n",
+ "Unexpected response code %u/%d for GET %s\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) rs.hr.ec,
+ rgh->url);
break;
}
if (NULL != rgh->cb)
{
rgh->cb (rgh->cb_cls,
- &hr,
- NULL,
- 0, NULL);
+ &rs);
rgh->cb = NULL;
}
TALER_EXCHANGE_reserves_get_cancel (rgh);
@@ -236,23 +181,20 @@ handle_reserves_get_finished (void *cls,
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)
{
struct TALER_EXCHANGE_ReservesGetHandle *rgh;
- struct GNUNET_CURL_Context *ctx;
CURL *eh;
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;
@@ -260,35 +202,33 @@ TALER_EXCHANGE_reserves_get (
end = GNUNET_STRINGS_data_to_string (
reserve_pub,
- sizeof (struct TALER_ReservePublicKeyP),
+ sizeof (*reserve_pub),
pub_str,
sizeof (pub_str));
*end = '\0';
GNUNET_snprintf (timeout_str,
sizeof (timeout_str),
- "%llu",
- (unsigned long long)
- (timeout.rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us));
- if (GNUNET_TIME_relative_is_zero (timeout))
+ "%u",
+ tms);
+ if (0 == tms)
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/reserves/%s",
+ "reserves/%s",
pub_str);
else
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/reserves/%s?timeout_ms=%s",
+ "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);
@@ -302,7 +242,13 @@ TALER_EXCHANGE_reserves_get (
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,
&handle_reserves_get_finished,
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 b9a9db941..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-2021 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.
@@ -84,25 +84,34 @@ check_transfers_get_response_ok (
struct TALER_EXCHANGE_TransfersGetHandle *wdh,
const json_t *json)
{
- json_t *details_j;
- struct TALER_EXCHANGE_TransferData td;
+ const json_t *details_j;
struct TALER_Amount total_expected;
struct TALER_MerchantPublicKeyP merchant_pub;
+ 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_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_json ("deposits", &details_j),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig", &td.exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub", &td.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 ()
};
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = json,
- .http_status = MHD_HTTP_OK
- };
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
@@ -113,32 +122,30 @@ check_transfers_get_response_ok (
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
- TALER_amount_set_zero (td.total_amount.currency,
+ TALER_amount_set_zero (td->total_amount.currency,
&total_expected))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
TALER_EXCHANGE_test_signing_key (
- TALER_EXCHANGE_get_keys (wdh->exchange),
- &td.exchange_pub))
+ wdh->keys,
+ &td->exchange_pub))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
- td.details_length = json_array_size (details_j);
+ td->details_length = json_array_size (details_j);
{
struct GNUNET_HashContext *hash_context;
struct TALER_TrackTransferDetails *details;
- details = GNUNET_new_array (td.details_length,
+ details = GNUNET_new_array (td->details_length,
struct TALER_TrackTransferDetails);
- td.details = details;
+ td->details = details;
hash_context = GNUNET_CRYPTO_hash_context_start ();
- for (unsigned int i = 0; i<td.details_length; 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);
@@ -146,21 +153,27 @@ 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_any ("deposit_value", &detail->coin_value),
- TALER_JSON_spec_amount_any ("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 ()
};
+ 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)) ||
- (GNUNET_OK !=
- TALER_amount_cmp_currency (&total_expected,
- &detail->coin_value)) ||
- (GNUNET_OK !=
- TALER_amount_cmp_currency (&total_expected,
- &detail->coin_fee)) ||
(0 >
TALER_amount_add (&total_expected,
&total_expected,
@@ -172,51 +185,35 @@ check_transfers_get_response_ok (
{
GNUNET_break_op (0);
GNUNET_CRYPTO_hash_context_abort (hash_context);
- GNUNET_JSON_parse_free (spec);
GNUNET_free (details);
return GNUNET_SYSERR;
}
/* build up big hash for signature checking later */
- {
- struct TALER_WireDepositDetailP dd;
-
- dd.h_contract_terms = detail->h_contract_terms;
- dd.execution_time = GNUNET_TIME_timestamp_hton (td.execution_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_CRYPTO_hash_context_read (hash_context,
- &dd,
- sizeof (dd));
- }
+ 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 */
{
- struct TALER_WireDepositDataPS wdp = {
- .purpose.purpose = htonl (
- TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT),
- .purpose.size = htonl (sizeof (wdp)),
- .merchant_pub = merchant_pub,
- .h_payto = td.h_payto
- };
+ struct GNUNET_HashCode h_details;
- TALER_amount_hton (&wdp.total,
- &td.total_amount);
- TALER_amount_hton (&wdp.wire_fee,
- &td.wire_fee);
GNUNET_CRYPTO_hash_context_finish (hash_context,
- &wdp.h_details);
+ &h_details);
if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (
- TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT,
- &wdp,
- &td.exchange_sig.eddsa_signature,
- &td.exchange_pub.eddsa_pub))
+ 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_JSON_parse_free (spec);
GNUNET_free (details);
return GNUNET_SYSERR;
}
@@ -225,29 +222,24 @@ check_transfers_get_response_ok (
if (0 >
TALER_amount_subtract (&total_expected,
&total_expected,
- &td.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,
- &td.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,
- &hr,
- &td);
+ &tgr);
GNUNET_free (details);
}
- GNUNET_JSON_parse_free (spec);
- TALER_EXCHANGE_transfers_get_cancel (wdh);
return GNUNET_OK;
}
@@ -267,100 +259,85 @@ handle_transfers_get_finished (void *cls,
{
struct TALER_EXCHANGE_TransfersGetHandle *wdh = cls;
const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ struct TALER_EXCHANGE_TransfersGetResponse tgr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
};
wdh->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_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);
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ tgr.hr.ec = TALER_JSON_get_error_code (j);
+ tgr.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
GNUNET_break_op (0);
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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/%d for transfers get\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) tgr.hr.ec);
break;
}
wdh->cb (wdh->cb_cls,
- &hr,
- 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;
@@ -376,11 +353,12 @@ 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);
@@ -394,7 +372,7 @@ TALER_EXCHANGE_transfers_get (
GNUNET_free (wdh);
return NULL;
}
- ctx = TEAH_handle_to_context (exchange);
+ wdh->keys = TALER_EXCHANGE_keys_incref (keys);
wdh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
eh,
&handle_transfers_get_finished,
@@ -419,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 453b576b1..000000000
--- a/src/lib/exchange_api_wire.c
+++ /dev/null
@@ -1,483 +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_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_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_any ("wire_fee",
- &wa->wire_fee),
- TALER_JSON_spec_amount_any ("closing_fee",
- &wa->closing_fee),
- GNUNET_JSON_spec_timestamp ("start_date",
- &wa->start_date),
- GNUNET_JSON_spec_timestamp ("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;
- const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
- };
-
- TALER_LOG_DEBUG ("Checking raw /wire response\n");
- wh->job = NULL;
- switch (response_code)
- {
- case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- /* FIXME: Maybe we should only increment when we know it's a timeout? */
- wh->exchange->wire_error_count++;
- 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 ()
- };
-
- wh->exchange->wire_error_count = 0;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (0 == (num_accounts = json_array_size (accounts)))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (NULL == (fm = parse_fees (fees)))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
-
- 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);
- hr.http_status = 0;
- hr.ec = TALER_EC_EXCHANGE_WIRE_SIGNATURE_INVALID;
- break;
- }
- if (GNUNET_OK !=
- GNUNET_JSON_parse (account,
- spec_account,
- NULL, NULL))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (NULL == (method = TALER_payto_get_method (wa->payto_uri)))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (NULL == (wa->fees = lookup_fee (fm,
- method)))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- GNUNET_free (method);
- break;
- }
- GNUNET_free (method);
- } /* end 'for all accounts */
- if ( (0 != response_code) &&
- (NULL != wh->cb) )
- {
- wh->cb (wh->cb_cls,
- &hr,
- num_accounts,
- was);
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- break;
- default:
- /* unexpected response code */
- if (MHD_HTTP_GATEWAY_TIMEOUT == response_code)
- wh->exchange->wire_error_count++;
- GNUNET_break_op (0);
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange wire\n",
- (unsigned int) response_code,
- (int) hr.ec);
- break;
- }
- if (NULL != wh->cb)
- wh->cb (wh->cb_cls,
- &hr,
- 0,
- NULL);
- TALER_EXCHANGE_wire_cancel (wh);
-}
-
-
-/**
- * Compute the network timeout for the next request to /wire.
- *
- * @param exchange the exchange handle
- * @returns the timeout in seconds (for use by CURL)
- */
-static long
-get_wire_timeout_seconds (struct TALER_EXCHANGE_Handle *exchange)
-{
- return GNUNET_MIN (60,
- 5 + (1L << exchange->wire_error_count));
-}
-
-
-/**
- * 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");
- if (NULL == wh->url)
- {
- GNUNET_free (wh);
- return NULL;
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (wh->url);
- GNUNET_free (wh);
- return NULL;
- }
- GNUNET_break (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_TIMEOUT,
- get_wire_timeout_seconds (wh->exchange)));
- ctx = TEAH_handle_to_context (exchange);
- wh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
- eh,
- &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 5e823ee6d..000000000
--- a/src/lib/exchange_api_withdraw.c
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file lib/exchange_api_withdraw.c
- * @brief Implementation of /reserves/$RESERVE_PUB/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"
-
-
-/**
- * @brief A Withdraw Handle
- */
-struct TALER_EXCHANGE_WithdrawHandle
-{
-
- /**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * Handle for the actual (internal) withdraw operation.
- */
- struct TALER_EXCHANGE_Withdraw2Handle *wh2;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_WithdrawCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Secrets of the planchet.
- */
- struct TALER_PlanchetSecretsP ps;
-
- /**
- * Denomination key we are withdrawing.
- */
- struct TALER_EXCHANGE_DenomPublicKey pk;
-
- /**
- * Hash of the public key of the coin we are signing.
- */
- struct TALER_CoinPubHash c_hash;
-
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reserves/$RESERVE_PUB/withdraw request.
- *
- * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
- * @param hr HTTP response data
- * @param blind_sig blind signature over the coin, NULL on error
- */
-static void
-handle_reserve_withdraw_finished (
- void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_BlindedDenominationSignature *blind_sig)
-{
- struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
- struct TALER_EXCHANGE_WithdrawResponse wr = {
- .hr = *hr
- };
-
- wh->wh2 = NULL;
- switch (hr->http_status)
- {
- case MHD_HTTP_OK:
- {
- struct TALER_FreshCoin fc;
-
- if (GNUNET_OK !=
- TALER_planchet_to_coin (&wh->pk.key,
- blind_sig,
- &wh->ps,
- &wh->c_hash,
- &fc))
- {
- wr.hr.http_status = 0;
- wr.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE;
- break;
- }
- wr.details.success.sig = fc.sig;
- break;
- }
- case MHD_HTTP_ACCEPTED:
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint64 ("payment_target_uuid",
- &wr.details.accepted.payment_target_uuid),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (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);
- if (MHD_HTTP_OK == hr->http_status)
- TALER_denom_sig_free (&wr.details.success.sig);
- TALER_EXCHANGE_withdraw_cancel (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_PlanchetDetail pd;
- struct TALER_EXCHANGE_WithdrawHandle *wh;
-
- wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle);
- wh->exchange = exchange;
- wh->cb = res_cb;
- wh->cb_cls = res_cb_cls;
- wh->pk = *pk;
- wh->ps = *ps;
- if (GNUNET_OK !=
- TALER_planchet_prepare (&pk->key,
- ps,
- &wh->c_hash,
- &pd))
- {
- GNUNET_break (0);
- GNUNET_free (wh);
- return NULL;
- }
- TALER_denom_pub_deep_copy (&wh->pk.key,
- &pk->key);
- wh->wh2 = TALER_EXCHANGE_withdraw2 (exchange,
- &pd,
- reserve_priv,
- &handle_reserve_withdraw_finished,
- wh);
- GNUNET_free (pd.coin_ev);
- return wh;
-}
-
-
-void
-TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh)
-{
- if (NULL != wh->wh2)
- {
- TALER_EXCHANGE_withdraw2_cancel (wh->wh2);
- wh->wh2 = NULL;
- }
- TALER_denom_pub_free (&wh->pk.key);
- GNUNET_free (wh);
-}
diff --git a/src/lib/exchange_api_withdraw2.c b/src/lib/exchange_api_withdraw2.c
deleted file mode 100644
index d50892e5b..000000000
--- a/src/lib/exchange_api_withdraw2.c
+++ /dev/null
@@ -1,513 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file lib/exchange_api_withdraw2.c
- * @brief Implementation of /reserves/$RESERVE_PUB/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 Withdraw Handle
- */
-struct TALER_EXCHANGE_Withdraw2Handle
-{
-
- /**
- * 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_Withdraw2Callback 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;
-
-};
-
-
-/**
- * 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 enum GNUNET_GenericReturnValue
-reserve_withdraw_ok (struct TALER_EXCHANGE_Withdraw2Handle *wh,
- const json_t *json)
-{
- struct TALER_BlindedDenominationSignature blind_sig;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_blinded_denom_sig ("ev_sig",
- &blind_sig),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = json,
- .http_status = MHD_HTTP_OK
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- /* signature is valid, return it to the application */
- wh->cb (wh->cb_cls,
- &hr,
- &blind_sig);
- /* make sure callback isn't called again after return */
- wh->cb = NULL;
- GNUNET_JSON_parse_free (spec);
- 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 enum GNUNET_GenericReturnValue
-reserve_withdraw_payment_required (
- struct TALER_EXCHANGE_Withdraw2Handle *wh,
- const json_t *json)
-{
- struct TALER_Amount balance;
- struct TALER_Amount balance_from_history;
- json_t *history;
- size_t len;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("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;
- }
- /* Check that funds were really insufficient */
- if (0 >= TALER_amount_cmp (&wh->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_Withdraw2Handle *wh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
- };
-
- wh->job = NULL;
- switch (response_code)
- {
- case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- reserve_withdraw_ok (wh,
- j))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- GNUNET_assert (NULL == wh->cb);
- TALER_EXCHANGE_withdraw2_cancel (wh);
- return;
- case MHD_HTTP_ACCEPTED:
- /* only validate reply is well-formed */
- {
- uint64_t ptu;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint64 ("payment_target_uuid",
- &ptu),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- 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. */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (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);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- }
- else
- {
- hr.ec = TALER_JSON_get_error_code (j);
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange withdraw\n",
- (unsigned int) response_code,
- (int) hr.ec);
- break;
- }
- if (NULL != wh->cb)
- {
- wh->cb (wh->cb_cls,
- &hr,
- NULL);
- wh->cb = NULL;
- }
- TALER_EXCHANGE_withdraw2_cancel (wh);
-}
-
-
-struct TALER_EXCHANGE_Withdraw2Handle *
-TALER_EXCHANGE_withdraw2 (
- struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_PlanchetDetail *pd,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- TALER_EXCHANGE_Withdraw2Callback res_cb,
- void *res_cb_cls)
-{
- struct TALER_EXCHANGE_Withdraw2Handle *wh;
- const struct TALER_EXCHANGE_Keys *keys;
- const struct TALER_EXCHANGE_DenomPublicKey *dk;
- struct TALER_ReserveSignatureP reserve_sig;
- char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
-
- keys = TALER_EXCHANGE_get_keys (exchange);
- if (NULL == keys)
- {
- GNUNET_break (0);
- return NULL;
- }
- dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
- &pd->denom_pub_hash);
- if (NULL == dk)
- {
- GNUNET_break (0);
- return NULL;
- }
- wh = GNUNET_new (struct TALER_EXCHANGE_Withdraw2Handle);
- wh->exchange = exchange;
- wh->cb = res_cb;
- wh->cb_cls = res_cb_cls;
- /* Compute how much we expected to charge to the reserve */
- if (0 >
- TALER_amount_add (&wh->requested_amount,
- &dk->value,
- &dk->fee_withdraw))
- {
- /* Overflow here? Very strange, our CPU must be fried... */
- GNUNET_break (0);
- GNUNET_free (wh);
- return NULL;
- }
-
- 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/withdraw",
- pub_str);
- }
- {
- struct TALER_WithdrawRequestPS req = {
- .purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS)),
- .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW),
- .reserve_pub = wh->reserve_pub,
- .h_denomination_pub = pd->denom_pub_hash
- };
-
- TALER_amount_hton (&req.amount_with_fee,
- &wh->requested_amount);
- TALER_coin_ev_hash (pd->coin_ev,
- pd->coin_ev_size,
- &req.h_coin_envelope);
- GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
- &req,
- &reserve_sig.eddsa_signature);
- }
-
- {
- json_t *withdraw_obj;
-
- withdraw_obj = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("denom_pub_hash",
- &pd->denom_pub_hash),
- GNUNET_JSON_pack_data_varsize ("coin_ev",
- pd->coin_ev,
- pd->coin_ev_size),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &reserve_sig));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Attempting to withdraw from reserve %s\n",
- TALER_B2S (&wh->reserve_pub));
- wh->url = TEAH_path_to_url (exchange,
- arg_str);
- if (NULL == wh->url)
- {
- json_decref (withdraw_obj);
- GNUNET_free (wh);
- return NULL;
- }
- {
- CURL *eh;
- struct GNUNET_CURL_Context *ctx;
-
- ctx = TEAH_handle_to_context (exchange);
- eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
- if ( (NULL == eh) ||
- (GNUNET_OK !=
- TALER_curl_easy_post (&wh->post_ctx,
- eh,
- withdraw_obj)) )
- {
- GNUNET_break (0);
- if (NULL != eh)
- curl_easy_cleanup (eh);
- json_decref (withdraw_obj);
- GNUNET_free (wh->url);
- GNUNET_free (wh);
- return NULL;
- }
- json_decref (withdraw_obj);
- wh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- wh->post_ctx.headers,
- &handle_reserve_withdraw_finished,
- wh);
- }
- }
- return wh;
-}
-
-
-void
-TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *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/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 f7f052d51..cd3fe7701 100644
--- a/src/mhd/Makefile.am
+++ b/src/mhd/Makefile.am
@@ -16,12 +16,12 @@ libtalermhd_la_SOURCES = \
mhd_responses.c \
mhd_run.c
libtalermhd_la_LDFLAGS = \
- -version-info 0:0:0 \
+ -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 \
diff --git a/src/mhd/mhd_config.c b/src/mhd/mhd_config.c
index 0e9f2e088..31ec3e476 100644
--- a/src/mhd/mhd_config.c
+++ b/src/mhd/mhd_config.c
@@ -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,
diff --git a/src/mhd/mhd_legal.c b/src/mhd/mhd_legal.c
index 2ef36957d..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, 2020 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
@@ -27,6 +27,11 @@
#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.
@@ -121,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) ) ||
@@ -133,8 +146,9 @@ mime_matches (const char *accept_pattern,
mime,
da - accept_pattern)) ) ) &&
( (0 == strcmp (da, "/*")) ||
- (0 == strcasecmp (da,
- dm)) );
+ (0 == strncasecmp (da,
+ dm,
+ end - da)) );
}
@@ -145,9 +159,9 @@ TALER_MHD_xmime_matches (const char *accept_pattern,
char *ap = GNUNET_strdup (accept_pattern);
char *sptr;
- for (const char *tok = strtok_r (ap, ";", &sptr);
+ for (const char *tok = strtok_r (ap, ",", &sptr);
NULL != tok;
- tok = strtok_r (NULL, ";", &sptr))
+ tok = strtok_r (NULL, ",", &sptr))
{
if (mime_matches (tok,
mime))
@@ -167,7 +181,25 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn,
{
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;
@@ -186,6 +218,15 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn,
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);
@@ -196,6 +237,7 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn,
}
t = NULL;
+ langs = NULL;
if (NULL != legal)
{
const char *mime;
@@ -205,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);
@@ -221,6 +263,21 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn,
(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) ||
(! TALER_MHD_xmime_matches (mime,
t->mime_type)) ||
@@ -278,6 +335,30 @@ TALER_MHD_reply_legal (struct MHD_Connection *conn,
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,
@@ -287,6 +368,10 @@ 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));
{
MHD_RESULT ret;
@@ -320,9 +405,10 @@ load_terms (struct TALER_MHD_Legal *legal,
const char *mime;
unsigned int priority;
} mm[] = {
+ { .ext = ".txt", .mime = "text/plain", .priority = 150 },
{ .ext = ".html", .mime = "text/html", .priority = 100 },
{ .ext = ".htm", .mime = "text/html", .priority = 99 },
- { .ext = ".txt", .mime = "text/plain", .priority = 50 },
+ { .ext = ".md", .mime = "text/markdown", .priority = 50 },
{ .ext = ".pdf", .mime = "application/pdf", .priority = 25 },
{ .ext = ".jpg", .mime = "image/jpeg" },
{ .ext = ".jpeg", .mime = "image/jpeg" },
@@ -350,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,
@@ -415,6 +501,9 @@ 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);
{
void *buf;
size_t bsize;
@@ -515,7 +604,10 @@ 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);
@@ -578,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);
diff --git a/src/mhd/mhd_parsing.c b/src/mhd/mhd_parsing.c
index 4415c82a8..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--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 Affero General Public License as published by the Free Software
@@ -27,29 +27,6 @@
#include "taler_mhd_lib.h"
-/**
- * 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)
- */
enum GNUNET_GenericReturnValue
TALER_MHD_parse_post_json (struct MHD_Connection *connection,
void **con_cls,
@@ -83,7 +60,7 @@ TALER_MHD_parse_post_json (struct MHD_Connection *connection,
GNUNET_break (NULL == *json);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Closing connection, upload too large\n");
- return MHD_NO;
+ return GNUNET_SYSERR;
case GNUNET_JSON_PR_JSON_INVALID:
GNUNET_break (NULL == *json);
return (MHD_YES ==
@@ -102,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)
{
@@ -117,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_GENERIC_PARAMETER_MISSING,
- param_name))
- ? GNUNET_SYSERR : GNUNET_NO;
+ *present = false;
+ return GNUNET_OK;
}
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (str,
@@ -162,24 +132,186 @@ TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
param_name))
? GNUNET_SYSERR : GNUNET_NO;
+ *present = true;
+ return GNUNET_OK;
+}
+
+
+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;
}
-/**
- * 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
- */
enum GNUNET_GenericReturnValue
TALER_MHD_parse_json_data (struct MHD_Connection *connection,
const json_t *root,
@@ -217,24 +349,6 @@ TALER_MHD_parse_json_data (struct MHD_Connection *connection,
}
-/**
- * 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,
@@ -272,21 +386,6 @@ TALER_MHD_parse_internal_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
- */
enum GNUNET_GenericReturnValue
TALER_MHD_parse_json_array (struct MHD_Connection *connection,
const json_t *root,
@@ -354,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 a639f4052..7dd6824e2 100644
--- a/src/mhd/mhd_responses.c
+++ b/src/mhd/mhd_responses.c
@@ -502,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
index 7747358ff..8388fbff6 100644
--- a/src/mhd/mhd_run.c
+++ b/src/mhd/mhd_run.c
@@ -162,8 +162,8 @@ TALER_MHD_daemon_trigger (void)
if (NULL != mhd_task)
{
GNUNET_SCHEDULER_cancel (mhd_task);
- mhd_task = NULL;
- run_daemon (NULL);
+ mhd_task = GNUNET_SCHEDULER_add_now (&run_daemon,
+ NULL);
}
else
{
diff --git a/src/pq/Makefile.am b/src/pq/Makefile.am
index b0717dfc7..4b192d762 100644
--- a/src/pq/Makefile.am
+++ b/src/pq/Makefile.am
@@ -10,11 +10,13 @@ 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 \
+ -lgnunetpq \
-lpq \
$(XLIB)
libtalerpq_la_LDFLAGS = \
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 37d7bf5be..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, 2021 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,43 +43,76 @@
* @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;
}
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 = {
- .conv = &qconv_amount_nbo,
- .data = x,
- .size = sizeof (*x),
- .num_params = 2
+ .conv_cls = (void *) db,
+ .conv = &qconv_amount_currency_tuple,
+ .data = amount,
+ .size = sizeof (*amount),
+ .num_params = 1,
};
return res;
@@ -85,7 +120,7 @@ TALER_PQ_query_param_amount_nbo (const struct TALER_AmountNBO *x)
/**
- * 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`
@@ -99,49 +134,74 @@ 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;
}
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 = {
- .conv = &qconv_amount,
- .data = x,
- .size = sizeof (*x),
- .num_params = 2
+ .conv_cls = (void *) db,
+ .conv = &qconv_amount_tuple,
+ .data = amount,
+ .size = sizeof (*amount),
+ .num_params = 1,
};
return res;
@@ -174,6 +234,7 @@ qconv_denom_pub (void *cls,
unsigned int scratch_length)
{
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];
@@ -185,33 +246,39 @@ qconv_denom_pub (void *cls,
GNUNET_assert (1 == param_length);
GNUNET_assert (scratch_length > 0);
GNUNET_break (NULL == cls);
- be[0] = htonl ((uint32_t) denom_pub->cipher);
- be[1] = htonl (denom_pub->age_mask.mask);
- switch (denom_pub->cipher)
+ be[0] = htonl ((uint32_t) bsp->cipher);
+ be[1] = htonl (denom_pub->age_mask.bits);
+ switch (bsp->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
tlen = GNUNET_CRYPTO_rsa_public_key_encode (
- denom_pub->details.rsa_public_key,
+ bsp->details.rsa_public_key,
&tbuf);
break;
- // TODO: add case for Clause-Schnorr
+ 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);
- memcpy (buf,
- be,
- sizeof (be));
- switch (denom_pub->cipher)
+ GNUNET_memcpy (buf,
+ be,
+ sizeof (be));
+ switch (bsp->cipher)
{
- case TALER_DENOMINATION_RSA:
- memcpy (&buf[sizeof (be)],
- tbuf,
- tlen);
+ case GNUNET_CRYPTO_BSA_RSA:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ tbuf,
+ tlen);
GNUNET_free (tbuf);
break;
- // TODO: add case for Clause-Schnorr
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ &bsp->details.cs_public_key,
+ tlen);
+ break;
default:
GNUNET_assert (0);
}
@@ -264,6 +331,7 @@ qconv_denom_sig (void *cls,
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];
@@ -275,33 +343,39 @@ qconv_denom_sig (void *cls,
GNUNET_assert (1 == param_length);
GNUNET_assert (scratch_length > 0);
GNUNET_break (NULL == cls);
- be[0] = htonl ((uint32_t) denom_sig->cipher);
+ be[0] = htonl ((uint32_t) ubs->cipher);
be[1] = htonl (0x00); /* magic marker: unblinded */
- switch (denom_sig->cipher)
+ switch (ubs->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
tlen = GNUNET_CRYPTO_rsa_signature_encode (
- denom_sig->details.rsa_signature,
+ ubs->details.rsa_signature,
&tbuf);
break;
- // TODO: add case for Clause-Schnorr
+ case GNUNET_CRYPTO_BSA_CS:
+ tlen = sizeof (ubs->details.cs_signature);
+ break;
default:
GNUNET_assert (0);
}
len = tlen + sizeof (be);
buf = GNUNET_malloc (len);
- memcpy (buf,
- &be,
- sizeof (be));
- switch (denom_sig->cipher)
+ GNUNET_memcpy (buf,
+ &be,
+ sizeof (be));
+ switch (ubs->cipher)
{
- case TALER_DENOMINATION_RSA:
- memcpy (&buf[sizeof (be)],
- tbuf,
- tlen);
+ case GNUNET_CRYPTO_BSA_RSA:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ tbuf,
+ tlen);
GNUNET_free (tbuf);
break;
- // TODO: add case for Clause-Schnorr
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ &ubs->details.cs_signature,
+ tlen);
+ break;
default:
GNUNET_assert (0);
}
@@ -354,6 +428,7 @@ qconv_blinded_denom_sig (void *cls,
unsigned int scratch_length)
{
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];
@@ -365,33 +440,39 @@ qconv_blinded_denom_sig (void *cls,
GNUNET_assert (1 == param_length);
GNUNET_assert (scratch_length > 0);
GNUNET_break (NULL == cls);
- be[0] = htonl ((uint32_t) denom_sig->cipher);
+ be[0] = htonl ((uint32_t) bs->cipher);
be[1] = htonl (0x01); /* magic marker: blinded */
- switch (denom_sig->cipher)
+ switch (bs->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
tlen = GNUNET_CRYPTO_rsa_signature_encode (
- denom_sig->details.blinded_rsa_signature,
+ bs->details.blinded_rsa_signature,
&tbuf);
break;
- // TODO: add case for Clause-Schnorr
+ 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);
- memcpy (buf,
- &be,
- sizeof (be));
- switch (denom_sig->cipher)
+ GNUNET_memcpy (buf,
+ &be,
+ sizeof (be));
+ switch (bs->cipher)
{
- case TALER_DENOMINATION_RSA:
- memcpy (&buf[sizeof (be)],
- tbuf,
- tlen);
+ case GNUNET_CRYPTO_BSA_RSA:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ tbuf,
+ tlen);
GNUNET_free (tbuf);
break;
- // TODO: add case for Clause-Schnorr
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_memcpy (&buf[sizeof (be)],
+ &bs->details.blinded_cs_answer,
+ tlen);
+ break;
default:
GNUNET_assert (0);
}
@@ -422,6 +503,188 @@ TALER_PQ_query_param_blinded_denom_sig (
* Function called to convert input argument into SQL parameters.
*
* @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_blinded_planchet (
+ const struct TALER_BlindedPlanchet *bp)
+{
+ struct GNUNET_PQ_QueryParam res = {
+ .conv = &qconv_blinded_planchet,
+ .data = bp,
+ .num_params = 1
+ };
+
+ return res;
+}
+
+
+/**
+ * Function called to convert input argument into SQL parameters.
+ *
+ * @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_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 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) data_len;
+ GNUNET_assert (1 == param_length);
+ GNUNET_assert (scratch_length > 0);
+ GNUNET_break (NULL == cls);
+ 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 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;
+}
+
+
+/**
+ * Function called to convert input argument into SQL parameters.
+ *
+ * @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
@@ -474,4 +737,603 @@ TALER_PQ_query_param_json (const json_t *x)
}
+/** ------------------- 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 a6bc9409a..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, 2021 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,150 +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 enum GNUNET_GenericReturnValue
-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)
+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))
{
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;
+
+ 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;
+ }
-/**
- * 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 enum GNUNET_GenericReturnValue
-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;
- enum GNUNET_GenericReturnValue ret;
+ 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? */
- if (sizeof (struct TALER_AmountNBO) != *dst_size)
+ 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;
}
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
@@ -174,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
@@ -188,47 +182,125 @@ TALER_PQ_result_spec_amount_nbo (const char *name,
* #GNUNET_SYSERR if a result was invalid (non-existing field)
*/
static enum GNUNET_GenericReturnValue
-extract_amount (void *cls,
- PGresult *result,
- int row,
- const char *fname,
- size_t *dst_size,
- void *dst)
+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;
- enum GNUNET_GenericReturnValue 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);
- if (GNUNET_OK == ret)
- TALER_amount_ntoh (r_amount,
- &amount_nbo);
- else
- memset (r_amount,
- 0,
- sizeof (struct TALER_Amount));
- 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;
}
@@ -238,7 +310,7 @@ TALER_PQ_result_spec_amount (const char *name,
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),
@@ -357,7 +429,7 @@ TALER_PQ_result_spec_json (const char *name,
*
* @param cls closure
* @param result where to extract data from
- * @param int row 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
@@ -374,6 +446,7 @@ extract_denom_pub (void *cls,
void *dst)
{
struct TALER_DenominationPublicKey *pk = dst;
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bpk;
size_t len;
const char *res;
int fnum;
@@ -406,29 +479,52 @@ extract_denom_pub (void *cls,
GNUNET_break (0);
return GNUNET_SYSERR;
}
- memcpy (be,
- res,
- sizeof (be));
+ GNUNET_memcpy (be,
+ res,
+ sizeof (be));
res += sizeof (be);
len -= sizeof (be);
- pk->cipher = ntohl (be[0]);
- pk->age_mask.mask = ntohl (be[1]);
- switch (pk->cipher)
+ 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 TALER_DENOMINATION_RSA:
- pk->details.rsa_public_key
+ 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 == pk->details.rsa_public_key)
+ 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;
- // FIXME: add CS case!
- default:
- GNUNET_break (0);
}
+ GNUNET_break (0);
+ GNUNET_free (bpk);
return GNUNET_SYSERR;
}
@@ -471,7 +567,7 @@ TALER_PQ_result_spec_denom_pub (const char *name,
*
* @param cls closure
* @param result where to extract data from
- * @param int row 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
@@ -488,6 +584,7 @@ extract_denom_sig (void *cls,
void *dst)
{
struct TALER_DenominationSignature *sig = dst;
+ struct GNUNET_CRYPTO_UnblindedSignature *ubs;
size_t len;
const char *res;
int fnum;
@@ -520,9 +617,9 @@ extract_denom_sig (void *cls,
GNUNET_break (0);
return GNUNET_SYSERR;
}
- memcpy (&be,
- res,
- sizeof (be));
+ GNUNET_memcpy (&be,
+ res,
+ sizeof (be));
if (0x00 != ntohl (be[1]))
{
GNUNET_break (0);
@@ -530,23 +627,40 @@ extract_denom_sig (void *cls,
}
res += sizeof (be);
len -= sizeof (be);
- sig->cipher = ntohl (be[0]);
- switch (sig->cipher)
+ ubs = GNUNET_new (struct GNUNET_CRYPTO_UnblindedSignature);
+ ubs->rc = 1;
+ ubs->cipher = ntohl (be[0]);
+ switch (ubs->cipher)
{
- case TALER_DENOMINATION_RSA:
- sig->details.rsa_signature
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ ubs->details.rsa_signature
= GNUNET_CRYPTO_rsa_signature_decode (res,
len);
- if (NULL == sig->details.rsa_signature)
+ 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;
- // FIXME: add CS case!
- default:
- GNUNET_break (0);
}
+ GNUNET_break (0);
+ GNUNET_free (ubs);
return GNUNET_SYSERR;
}
@@ -589,7 +703,7 @@ TALER_PQ_result_spec_denom_sig (const char *name,
*
* @param cls closure
* @param result where to extract data from
- * @param int row 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
@@ -606,6 +720,7 @@ extract_blinded_denom_sig (void *cls,
void *dst)
{
struct TALER_BlindedDenominationSignature *sig = dst;
+ struct GNUNET_CRYPTO_BlindedSignature *bs;
size_t len;
const char *res;
int fnum;
@@ -638,9 +753,9 @@ extract_blinded_denom_sig (void *cls,
GNUNET_break (0);
return GNUNET_SYSERR;
}
- memcpy (&be,
- res,
- sizeof (be));
+ GNUNET_memcpy (&be,
+ res,
+ sizeof (be));
if (0x01 != ntohl (be[1])) /* magic marker: blinded */
{
GNUNET_break (0);
@@ -648,23 +763,40 @@ extract_blinded_denom_sig (void *cls,
}
res += sizeof (be);
len -= sizeof (be);
- sig->cipher = ntohl (be[0]);
- switch (sig->cipher)
+ bs = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature);
+ bs->rc = 1;
+ bs->cipher = ntohl (be[0]);
+ switch (bs->cipher)
{
- case TALER_DENOMINATION_RSA:
- sig->details.blinded_rsa_signature
+ 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 == sig->details.blinded_rsa_signature)
+ 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;
- // FIXME: add CS case!
- default:
- GNUNET_break (0);
}
+ GNUNET_break (0);
+ GNUNET_free (bs);
return GNUNET_SYSERR;
}
@@ -703,4 +835,743 @@ TALER_PQ_result_spec_blinded_denom_sig (
}
+/**
+ * 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/sq_query_helper.c b/src/sq/sq_query_helper.c
index d4b2d060d..711e63816 100644
--- a/src/sq/sq_query_helper.c
+++ b/src/sq/sq_query_helper.c
@@ -150,7 +150,7 @@ qconv_json (void *cls,
if (SQLITE_OK != sqlite3_bind_text (stmt,
(int) off,
str,
- strlen (str) + 1,
+ strlen (str),
SQLITE_TRANSIENT))
return GNUNET_SYSERR;
GNUNET_free (str);
diff --git a/src/sq/sq_result_helper.c b/src/sq/sq_result_helper.c
index bacb7743a..9d80837bd 100644
--- a/src/sq/sq_result_helper.c
+++ b/src/sq/sq_result_helper.c
@@ -23,7 +23,7 @@
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_sq_lib.h>
#include "taler_sq_lib.h"
-#include "taler_amount_lib.h"
+#include "taler_util.h"
/**
diff --git a/src/sq/test_sq.c b/src/sq/test_sq.c
index 5f715ad6c..8f464faf3 100644
--- a/src/sq/test_sq.c
+++ b/src/sq/test_sq.c
@@ -63,7 +63,11 @@ run_queries (sqlite3 *db)
TALER_amount_hton (&namount,
&hamount);
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)));
GNUNET_assert (NULL != json);
GNUNET_assert (GNUNET_OK ==
GNUNET_SQ_prepare (db,
@@ -152,6 +156,8 @@ main (int argc,
sqlite3 *db;
int ret;
+ (void) argc;
+ (void) argv;
GNUNET_log_setup ("test-pq",
"WARNING",
NULL);
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 8c19b94c8..e1075ab16 100644
--- a/src/testing/.gitignore
+++ b/src/testing/.gitignore
@@ -1,13 +1,20 @@
-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
+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
@@ -24,12 +31,32 @@ 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 bc78217b3..195ab4c55 100644
--- a/src/testing/Makefile.am
+++ b/src/testing/Makefile.am
@@ -13,6 +13,9 @@ endif
clean-local:
rm -rf report*
+bin_SCRIPTS = \
+ taler-unified-setup.sh
+
# Libraries
lib_LTLIBRARIES = \
@@ -37,11 +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 \
@@ -52,59 +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_change_auth.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-offline.c \
testing_api_cmd_exec_closer.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_fees.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_set_wire_fee.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_revoke_denom_key.c \
testing_api_cmd_revoke_sign_key.c \
- testing_api_cmd_rewind.c \
- testing_api_cmd_serialize_keys.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_helpers_auditor.c \
- testing_api_helpers_bank.c \
- testing_api_helpers_exchange.c \
testing_api_loop.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,36 +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_bank_api_with_nexus \
- test_exchange_api \
- test_exchange_api_keys_cherry_picking \
- test_exchange_api_revocation \
- test_exchange_api_overlapping_keys_bug \
- test_exchange_management_api \
+ 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 \
@@ -172,7 +230,7 @@ test_bank_api_with_nexus_SOURCES = \
test_bank_api.c
test_bank_api_with_nexus_LDADD = \
libtalertesting.la \
- -ltalerexchange \
+ $(top_builddir)/src/lib/libtalerexchange.la \
-lgnunetutil \
$(top_builddir)/src/bank-lib/libtalerbank.la \
$(XLIB)
@@ -181,23 +239,30 @@ 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 \
$(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 \
- -lgnunetutil \
+ $(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_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) \
@@ -205,23 +270,31 @@ 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 \
$(XLIB)
-test_exchange_management_api_SOURCES = \
- test_exchange_management_api.c
-test_exchange_management_api_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_revocation_SOURCES = \
- test_exchange_api_revocation.c
-test_exchange_api_revocation_LDADD = \
+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) \
@@ -229,14 +302,139 @@ 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_SOURCES = \
+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) \
+ $(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_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 \
+ $(XLIB)
+
+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) \
+ $(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_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_cs_LDADD = \
libtalertesting.la \
$(top_builddir)/src/lib/libtalerexchange.la \
$(LIBGCRYPT_LIBS) \
@@ -248,9 +446,9 @@ test_exchange_api_keys_cherry_picking_LDADD = \
-ljansson \
$(XLIB)
-test_exchange_api_overlapping_keys_bug_SOURCES = \
+test_exchange_api_overlapping_keys_bug_rsa_SOURCES = \
test_exchange_api_overlapping_keys_bug.c
-test_exchange_api_overlapping_keys_bug_LDADD = \
+test_exchange_api_overlapping_keys_bug_rsa_LDADD = \
libtalertesting.la \
$(top_builddir)/src/lib/libtalerexchange.la \
$(LIBGCRYPT_LIBS) \
@@ -262,6 +460,25 @@ test_exchange_api_overlapping_keys_bug_LDADD = \
-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
test_taler_exchange_aggregator_postgres_LDADD = \
@@ -295,9 +512,9 @@ test_taler_exchange_wirewatch_postgres_LDADD = \
-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 \
@@ -312,24 +529,26 @@ test_exchange_api_twisted_LDADD = \
-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 \
$(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 \
@@ -339,10 +558,9 @@ 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 = \
@@ -362,28 +580,38 @@ test_kyc_api_LDADD = \
# Distribution
EXTRA_DIST = \
- test_auditor_api.conf \
- test_auditor_api_expire_reserve_now.conf \
+ $(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_exchange_api_home/.config/taler/account-2.json \
+ 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/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/master.priv \
- 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_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_expire_reserve_now.conf \
- test_taler_exchange_httpd_home/.config/taler/account-1.json \
- test_taler_exchange_httpd_home/.local/share/taler/exchange-offline/master.priv \
- 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_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_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 7f277629d..8c3ee4ba5 100644
--- a/src/testing/test-taler-exchange-aggregator-postgres.conf
+++ b/src/testing/test-taler-exchange-aggregator-postgres.conf
@@ -1,84 +1,59 @@
+# This file is in the public domain.
+
[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-exchange-secmod-rsa]
# Reduce from 1 year to speed up test
LOOKAHEAD_SIGN = 24 days
[taler-exchange-secmod-eddsa]
-# Reduce from 1 year to speed up test
LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
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 = "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"
+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/2/"
+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
[bank]
HTTP_PORT = 8082
@@ -92,4 +67,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-taler-exchange-wirewatch-postgres.conf b/src/testing/test-taler-exchange-wirewatch-postgres.conf
index d42f9d445..4f13077ac 100644
--- a/src/testing/test-taler-exchange-wirewatch-postgres.conf
+++ b/src/testing/test-taler-exchange-wirewatch-postgres.conf
@@ -1,68 +1,55 @@
+# This file is in the public domain.
+
[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-exchange-secmod-rsa]
-# Reduce from 1 year to speed up test
LOOKAHEAD_SIGN = 24 days
[taler-exchange-secmod-eddsa]
-# Reduce from 1 year to speed up test
LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
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
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
+
[exchange-account-1]
# What is the account URL?
-PAYTO_URI = "payto://x-taler-bank/localhost/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/2/"
+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
@@ -70,6 +57,16 @@ PASSWORD = x
[bank]
HTTP_PORT = 8082
+[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.
[coin_eur_ct_1]
@@ -81,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 90675dd92..3810c601e 100644
--- a/src/testing/test_auditor_api.c
+++ b/src/testing/test_auditor_api.c
@@ -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
@@ -71,8 +66,8 @@ static struct TALER_TESTING_BankConfiguration bc;
*/
#define CMD_EXEC_AGGREGATOR(label) \
TALER_TESTING_cmd_sleep (label "-sleep", 1), \
- TALER_TESTING_cmd_exec_aggregator (label, CONFIG_FILE), \
- TALER_TESTING_cmd_exec_transfer (label, CONFIG_FILE)
+ 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
@@ -83,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.
@@ -92,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)
/**
@@ -116,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.
@@ -128,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 ()
};
@@ -139,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",
@@ -156,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.
@@ -168,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
@@ -176,7 +173,7 @@ 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",
@@ -184,7 +181,7 @@ run (void *cls,
/**
* 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-1",
"refresh-withdraw-coin-1",
MHD_HTTP_OK,
@@ -201,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",
@@ -223,75 +220,75 @@ run (void *cls,
*/
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-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-99c",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:0.08",
- bc.exchange_payto,
- bc.user43_payto),
+ 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,
+ 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 ()
};
@@ -309,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,
@@ -347,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
@@ -355,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",
@@ -372,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",
@@ -402,11 +401,12 @@ 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",
@@ -417,6 +417,7 @@ 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
@@ -425,10 +426,11 @@ run (void *cls,
*/
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
@@ -447,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
@@ -454,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",
@@ -466,7 +470,7 @@ 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",
@@ -485,54 +489,65 @@ run (void *cls,
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",
"massive-withdraw-1",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
@@ -541,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",
@@ -550,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",
@@ -559,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",
@@ -568,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",
@@ -577,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",
@@ -586,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",
@@ -595,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",
@@ -604,7 +619,7 @@ 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",
@@ -613,15 +628,14 @@ run (void *cls,
"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"),
@@ -630,27 +644,25 @@ run (void *cls,
};
struct TALER_TESTING_Command commands[] = {
- TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
- CONFIG_FILE,
- "EUR:0.01",
- "EUR:0.01"),
- TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_wire_add ("add-wire-account",
- "payto://x-taler-bank/localhost/2",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
- CONFIG_FILE),
- TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
- 2),
+ 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),
+ 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",
@@ -672,9 +684,8 @@ run (void *cls,
};
(void) cls;
- TALER_TESTING_run_with_fakebank (is,
- commands,
- bc.exchange_auth.wire_gateway_url);
+ TALER_TESTING_run (is,
+ commands);
}
@@ -683,48 +694,27 @@ main (int argc,
char *const *argv)
{
(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",
- "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))
{
- 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 03a5e2453..000000000
--- a/src/testing/test_auditor_api.conf
+++ /dev/null
@@ -1,140 +0,0 @@
-
-# This file is in the public domain.
-#
-[PATHS]
-# Persistent data storage for the testcase
-TALER_TEST_HOME = test_exchange_api_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
-
-[taler-exchange-secmod-rsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-
-[taler-exchange-secmod-eddsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
-DURATION = 14 days
-
-
-[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]
-
-# 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/"
-
-[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.
-[exchange-account-1]
-# What is the URL of our account?
-PAYTO_URI = "payto://x-taler-bank/localhost/42"
-
-[exchange-accountcredentials-1]
-WIRE_GATEWAY_URL = "http://localhost:8082/42/"
-
-[bank]
-HTTP_PORT = 8082
-
-# ENABLE_CREDIT = YES
-
-[exchange-account-2]
-# What is the bank account (with the "Taler Bank" demo system)?
-PAYTO_URI = "payto://x-taler-bank/localhost/2"
-ENABLE_DEBIT = YES
-ENABLE_CREDIT = YES
-
-# Authentication information for basic authentication
-[exchange-accountcredentials-2]
-WIRE_GATEWAY_URL = "http://localhost:8082/2/"
-WIRE_GATEWAY_AUTH_METHOD = "basic"
-USERNAME = user
-PASSWORD = pass
-
-
-
-
-# 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 1b60b15d1..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,20 +86,16 @@ do_timeout (void *cls)
* Function called with information about the auditor.
*
* @param cls closure
- * @param hr http response details
- * @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_HttpResponse *hr,
- const struct TALER_AUDITOR_VersionInformation *vi,
- enum TALER_AUDITOR_VersionCompatibility compat)
+ const struct TALER_AUDITOR_ConfigResponse *vr)
{
(void) cls;
- (void) hr;
- if ( (NULL != vi) &&
- (TALER_AUDITOR_VC_MATCH == compat) )
+ 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;
@@ -117,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,
@@ -148,15 +149,16 @@ main (int argc,
"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");
@@ -166,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 d81fbe36e..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,34 +34,27 @@
#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.
- */
-static struct TALER_TESTING_BankConfiguration bc;
/**
- * Handle to the Py-bank daemon.
+ * Configuration file. It changes based on
+ * whether Nexus or Fakebank are used.
*/
-static struct GNUNET_OS_Process *bankd;
+static const char *cfgfile;
/**
- * Flag indicating whether the test is running against the
- * Fakebank. Set up at runtime.
+ * Our credentials.
*/
-static int with_fakebank;
+static struct TALER_TESTING_Credentials cred;
/**
- * Handles to the libeufin services.
+ * Which bank is the test running against?
+ * Set up at runtime.
*/
-static struct TALER_TESTING_LibeufinServices libeufin_services;
+static enum TALER_TESTING_BankSystem bs;
-/**
- * Needed to shutdown differently.
- */
-static int with_libeufin;
/**
* Main function that will tell the interpreter what commands to
@@ -74,52 +67,76 @@ run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct TALER_WireTransferIdentifierRawP wtid;
+ const char *ssoptions;
(void) cls;
- memset (&wtid, 42, sizeof (wtid));
+ 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",
- "KUDOS:2.01",
- &bc.exchange_auth,
- bc.user42_payto,
+ "EUR:2.01",
+ &cred.ba_admin,
+ cred.user42_payto,
"credit-1",
- MHD_HTTP_CONFLICT),
- TALER_TESTING_cmd_sleep ("Waiting 4s for 'credit-1' to settle",
- 4),
+ -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_sleep ("Waiting 5s for 'debit-1' to settle",
- 5),
TALER_TESTING_cmd_bank_debits ("history-2b",
- &bc.exchange_auth,
+ &cred.ba,
NULL,
5),
TALER_TESTING_cmd_end ()
@@ -127,152 +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)
{
- int rv;
- const char *cfgfile;
-
(void) argc;
- (void) argv;
- /* 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)
+ if (TALER_TESTING_has_in_name (argv[0],
+ "_with_fakebank"))
{
- TALER_LOG_DEBUG ("Running against the Fakebank.\n");
+ bs = TALER_TESTING_BS_FAKEBANK;
cfgfile = CONFIG_FILE_FAKEBANK;
- if (GNUNET_OK !=
- TALER_TESTING_prepare_fakebank (CONFIG_FILE_FAKEBANK,
- "exchange-account-2",
- &bc))
- {
- GNUNET_break (0);
- return 77;
- }
}
- else if (GNUNET_YES == TALER_TESTING_has_in_name (argv[0],
- "_with_pybank"))
+ else if (TALER_TESTING_has_in_name (argv[0],
+ "_with_nexus"))
{
- TALER_LOG_DEBUG ("Running against the Pybank.\n");
- cfgfile = CONFIG_FILE_PYBANK;
- if (GNUNET_OK !=
- TALER_TESTING_prepare_bank (CONFIG_FILE_PYBANK,
- GNUNET_YES,
- "exchange-account-2",
- &bc))
- {
- GNUNET_break (0);
- return 77;
- }
-
- if (NULL == (bankd = TALER_TESTING_run_bank (
- CONFIG_FILE_PYBANK,
- bc.exchange_auth.wire_gateway_url)))
- {
- GNUNET_break (0);
- return 77;
- }
- }
- else if (GNUNET_YES == TALER_TESTING_has_in_name (argv[0],
- "_with_nexus"))
- {
- TALER_LOG_DEBUG ("Running with Nexus.\n");
- with_libeufin = GNUNET_YES;
+ bs = TALER_TESTING_BS_IBAN;
cfgfile = CONFIG_FILE_NEXUS;
- if (GNUNET_OK !=
- TALER_TESTING_prepare_nexus (CONFIG_FILE_NEXUS,
- GNUNET_YES,
- "exchange-account-2",
- &bc))
+ if (GNUNET_SYSERR ==
+ GNUNET_OS_check_helper_binary ("libeufin-bank",
+ false,
+ NULL))
{
- GNUNET_break (0);
+ fprintf (stderr,
+ "libeufin-bank not found. Skipping test.\n");
return 77;
}
- libeufin_services = TALER_TESTING_run_libeufin (&bc);
- if ( (NULL == libeufin_services.nexus) ||
- (NULL == libeufin_services.sandbox) )
- return 77;
}
else
{
- /* no bank service was ever invoked. */
+ /* no bank service was specified. */
+ GNUNET_break (0);
return 77;
}
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_parse_and_run (cfgfile,
- &setup_with_cfg,
- NULL))
- rv = 1;
- else
- rv = 0;
-
- if (GNUNET_NO == with_fakebank)
- {
- // -> pybank
- if (GNUNET_NO == with_libeufin)
- {
-
- GNUNET_OS_process_kill (bankd,
- SIGKILL);
- GNUNET_OS_process_wait (bankd);
- GNUNET_OS_process_destroy (bankd);
- }
- else // -> libeufin
- {
- GNUNET_OS_process_kill (libeufin_services.nexus,
- SIGKILL);
- GNUNET_OS_process_wait (libeufin_services.nexus);
- GNUNET_OS_process_destroy (libeufin_services.nexus);
-
- GNUNET_OS_process_kill (libeufin_services.sandbox,
- SIGKILL);
- GNUNET_OS_process_wait (libeufin_services.sandbox);
- GNUNET_OS_process_destroy (libeufin_services.sandbox);
- }
- }
-
- 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 b97423987..62fa4cd4c 100644
--- a/src/testing/test_bank_api_fakebank.conf
+++ b/src/testing/test_bank_api_fakebank.conf
@@ -1,21 +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
+PAYTO_URI = "payto://x-taler-bank/localhost:8082/2?receiver-name=2"
[exchange-accountcredentials-2]
-WIRE_GATEWAY_URL = "http://localhost:8081/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
-
-[auditor]
-BASE_URL = "http://localhost:8083/"
+[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 4dab123a7..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,20 +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
-
-[auditor]
-BASE_URL = "http://localhost:8083/"
diff --git a/src/testing/test_bank_api_nexus.conf b/src/testing/test_bank_api_nexus.conf
index c514170e1..605c7b00e 100644
--- a/src/testing/test_bank_api_nexus.conf
+++ b/src/testing/test_bank_api_nexus.conf
@@ -1,23 +1,35 @@
# This file is in the public domain.
-
-[taler]
-currency = KUDOS
+@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:5001/facades/my-facade/taler/
+WIRE_GATEWAY_URL = http://localhost:8082/accounts/exchange/taler-wire-gateway/
WIRE_GATEWAY_AUTH_METHOD = basic
-# the exchange authenticates as the 'admin' user,
-# since that makes the test preparation just easier.
-USERNAME = Exchange
+USERNAME = exchange
PASSWORD = x
-[bank]
-# not (!) used by the nexus, only by the helper
-# check to make sure the port is free for the nexus.
-HTTP_PORT = 5001
+[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"
-[auditor]
-BASE_URL = "http://localhost:8083/"
+# 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 6603ba8a5..000000000
--- a/src/testing/test_bank_api_pybank.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-# This file is in the public domain.
-
-[taler]
-currency = KUDOS
-
-[exchange-account-2]
-PAYTO_URI = payto://x-taler-bank/localhost/Exchange
-
-[exchange-accountcredentials-2]
-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
-
-[auditor]
-BASE_URL = "http://localhost:8083/"
diff --git a/src/testing/test_bank_api_pybank_twisted.conf b/src/testing/test_bank_api_pybank_twisted.conf
deleted file mode 100644
index d89cf0469..000000000
--- a/src/testing/test_bank_api_pybank_twisted.conf
+++ /dev/null
@@ -1,43 +0,0 @@
-[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
-
-[auditor]
-BASE_URL = "http://localhost:8083/"
-
-[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
-
-[exchange-accountcredentials-2]
-WIRE_GATEWAY_URL = "http://localhost:8888/taler-wire-gateway/Exchange/"
-WIRE_GATEWAY_AUTH_METHOD = basic
-USERNAME = Exchange
-PASSWORD = x
diff --git a/src/testing/test_bank_api_twisted.c b/src/testing/test_bank_api_twisted.c
index c550aaeb2..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
@@ -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
@@ -83,176 +79,118 @@ run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct TALER_WireTransferIdentifierRawP wtid;
- /* Route our commands through twister. */
+ /* 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));
- memcpy (&exchange_auth_twisted,
- &bc.exchange_auth,
- sizeof (struct TALER_BANK_AuthenticationData));
- if (with_fakebank)
- exchange_auth_twisted.wire_gateway_url =
- "http://localhost:8888/2/";
- else
- exchange_auth_twisted.wire_gateway_url =
- "http://localhost:8888/taler-wire-gateway/Exchange/";
+ 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[] = {
- /* Test retrying transfer after failure. */
- TALER_TESTING_cmd_malform_response ("malform-transfer",
- CONFIG_FILE_FAKEBANK),
- TALER_TESTING_cmd_transfer_retry (
- TALER_TESTING_cmd_transfer ("debit-1",
- "KUDOS:3.22",
- &exchange_auth_twisted,
- bc.exchange_payto,
- bc.user42_payto,
- &wtid,
- "http://exchange.example.com/")),
- TALER_TESTING_cmd_end ()
- };
+ {
+ 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 ()
+ };
- 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);
}
-/**
- * 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)
{
int ret;
- const char *cfgfilename;
(void) argc;
- /* 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);
-
- 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)))
+ 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;
}
- sleep (5);
- ret = GNUNET_CONFIGURATION_parse_and_run (cfgfilename,
- &setup_with_cfg,
- NULL);
+ /* 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_exchange_api-twisted.conf b/src/testing/test_exchange_api-twisted.conf
new file mode 100644
index 000000000..536d36ee4
--- /dev/null
+++ b/src/testing/test_exchange_api-twisted.conf
@@ -0,0 +1,29 @@
+# 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
+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
diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c
index 59c2cb06d..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[] = {
@@ -122,30 +115,45 @@ run (void *cls,
*/
CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1",
"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:6.02",
- bc.user42_payto,
- bc.exchange_payto,
+ cred.user42_payto,
+ cred.exchange_payto,
"create-reserve-1"),
/**
* 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),
/**
* 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),
/**
@@ -161,6 +169,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
"create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_end ()
};
@@ -172,29 +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),
+ 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,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:1",
- MHD_HTTP_CONFLICT),
+ 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",
@@ -208,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",
@@ -219,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",
@@ -230,7 +253,10 @@ run (void *cls,
struct TALER_TESTING_Command refresh[] = {
/**
* Try to melt the coin that shared the private key with another
- * coin (should fail). */
+ * 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,
@@ -241,8 +267,8 @@ run (void *cls,
"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.
@@ -254,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",
@@ -298,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",
@@ -309,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",
@@ -330,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,
@@ -356,37 +447,65 @@ run (void *cls,
TALER_TESTING_cmd_track_transfer_empty ("wire-deposit-failing",
NULL,
MHD_HTTP_NOT_FOUND),
- TALER_TESTING_cmd_sleep ("sleep-before-aggregator",
- 1),
/* 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",
+ cred.exchange_payto,
+ cred.user42_payto),
+ TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c4",
+ 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-08c",
+ cred.exchange_url,
"EUR:0.08",
- bc.exchange_payto,
- bc.user43_payto),
+ cred.exchange_payto,
+ cred.user43_payto),
+ TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-08c2",
+ cred.exchange_url,
+ "EUR:0.08",
+ 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",
@@ -406,7 +525,6 @@ run (void *cls,
TALER_TESTING_cmd_end ()
};
-
/**
* This block checks whether a wire deadline
* very far in the future does NOT get aggregated now.
@@ -419,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,
@@ -439,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
@@ -457,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",
@@ -476,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",
@@ -484,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 ()
};
@@ -501,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.
@@ -512,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
@@ -520,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",
@@ -533,7 +750,7 @@ 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_with_id ("refund-ok",
MHD_HTTP_OK,
@@ -554,7 +771,7 @@ run (void *cls,
TALER_TESTING_cmd_deposit ("deposit-refund-insufficient-refund",
"withdraw-coin-r1",
0,
- bc.user42_payto,
+ cred.user42_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:4\"}]}",
GNUNET_TIME_UNIT_MINUTES,
"EUR:4",
@@ -571,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",
@@ -585,10 +802,10 @@ 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.
*/
@@ -605,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",
@@ -633,7 +851,7 @@ run (void *cls,
* 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"),
@@ -650,8 +868,8 @@ run (void *cls,
TALER_TESTING_cmd_check_bank_admin_transfer (
"recoup-create-reserve-1-check",
"EUR:15.02",
- bc.user42_payto,
- bc.exchange_payto,
+ cred.user42_payto,
+ cred.exchange_payto,
"recoup-create-reserve-1"),
/**
* Run wire-watch to trigger the reserve creation.
@@ -661,11 +879,13 @@ 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),
/* melt 10 EUR coin to get 5 EUR refreshed coin */
TALER_TESTING_cmd_melt ("recoup-melt-coin-1b",
@@ -680,7 +900,7 @@ run (void *cls,
TALER_TESTING_cmd_revoke ("revoke-0-EUR:5",
MHD_HTTP_OK,
"recoup-withdraw-coin-1",
- CONFIG_FILE),
+ config_file),
/* Recoup coin to reserve */
TALER_TESTING_cmd_recoup ("recoup-1",
MHD_HTTP_OK,
@@ -756,6 +976,7 @@ 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),
/**
* This withdrawal will test the logic to create a "recoup"
@@ -764,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",
@@ -776,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",
@@ -796,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"),
@@ -820,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",
@@ -837,8 +1063,10 @@ run (void *cls,
TALER_TESTING_cmd_revoke ("revoke-1-EUR:1",
MHD_HTTP_OK,
"recoup-withdraw-coin-2a",
- CONFIG_FILE),
- /* Check recoup is failing for the coin with the reused coin key */
+ 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",
@@ -855,7 +1083,7 @@ run (void *cls,
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",
@@ -867,7 +1095,7 @@ run (void *cls,
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",
@@ -878,13 +1106,14 @@ run (void *cls,
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",
+ 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"),
@@ -892,6 +1121,75 @@ run (void *cls,
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
@@ -908,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");
@@ -927,30 +1226,30 @@ run (void *cls,
{
struct TALER_TESTING_Command commands[] = {
- /* setup exchange */
- TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_wire_add ("add-wire-account",
- "payto://x-taler-bank/localhost/2",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
- CONFIG_FILE),
- TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
- CONFIG_FILE,
- "EUR:0.01",
- "EUR:0.01"),
- TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
- 1),
- 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",
@@ -959,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",
@@ -967,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);
}
}
@@ -979,48 +1279,29 @@ main (int argc,
char *const *argv)
{
(void) argc;
- (void) 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))
{
- 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 a1b743658..c25f5091a 100644
--- a/src/testing/test_exchange_api.conf
+++ b/src/testing/test_exchange_api.conf
@@ -2,69 +2,80 @@
#
[PATHS]
-# Persistent data storage for the testcase
TALER_TEST_HOME = test_exchange_api_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
-[taler-exchange-secmod-rsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
+[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-exchange-secmod-eddsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
-DURATION = 14 days
[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
-[exchange]
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
-TERMS_ETAG = 0
-PRIVACY_ETAG = 0
+[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/"
+EXPIRE_SHARD_SIZE ="300 ms"
+EXPIRE_IDLE_SLEEP_INTERVAL ="1 s"
[exchangedb-postgres]
CONFIG = "postgres:///talercheck"
-[auditordb-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"
+
-# Sections starting with "exchange-account-" configure the bank accounts
-# of the exchange. The "URL" specifies the account in
-# payto://-format.
[exchange-account-1]
-# What is the URL of our account?
-PAYTO_URI = "payto://x-taler-bank/localhost/42"
-# ENABLE_CREDIT = YES
+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:9081/42/"
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
+
+[admin-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"
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
ENABLE_DEBIT = YES
ENABLE_CREDIT = YES
@@ -72,64 +83,36 @@ ENABLE_CREDIT = YES
WIRE_GATEWAY_AUTH_METHOD = basic
USERNAME = Exchange
PASSWORD = x
-WIRE_GATEWAY_URL = "http://localhost:9081/2/"
+WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
-[bank]
-HTTP_PORT = 9081
-
-# 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
index 394926938..391b6ea73 100644
--- 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
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_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 6ab17c634..2919ea8d5 100644
--- a/src/testing/test_exchange_api_keys_cherry_picking.c
+++ b/src/testing/test_exchange_api_keys_cherry_picking.c
@@ -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,12 +40,12 @@ 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;
/**
- * Exchange configuration data.
+ * Our credentials.
*/
-static struct TALER_TESTING_ExchangeConfiguration ec;
+static struct TALER_TESTING_Credentials cred;
/**
@@ -58,44 +59,27 @@ run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct TALER_TESTING_Command commands[] = {
- TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_wire_add ("add-wire-account",
- "payto://x-taler-bank/localhost/2",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
- CONFIG_FILE,
- "EUR:0.01",
- "EUR:0.01"),
- TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
- CONFIG_FILE),
- TALER_TESTING_cmd_check_keys_pull_all_keys ("initial-/keys",
- 1),
+ 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_check_keys ("check-keys-1",
- 2 /* generation */),
- TALER_TESTING_cmd_check_keys_with_last_denom ("check-keys-2",
- 3 /* generation */,
- "check-keys-1"),
- 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 ("check-keys-after-deserialization",
- 4),
- /**
- * Use one of the deserialized keys.
- */
- TALER_TESTING_cmd_wire ("wire-with-serialized-keys",
- "x-taler-bank",
- NULL,
- MHD_HTTP_OK),
+ 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 ()
};
@@ -110,42 +94,24 @@ main (int argc,
char *const *argv)
{
(void) argc;
- (void) 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))
{
- 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 d7dd95352..142242424 100644
--- a/src/testing/test_exchange_api_keys_cherry_picking.conf
+++ b/src/testing/test_exchange_api_keys_cherry_picking.conf
@@ -3,52 +3,23 @@
[PATHS]
# Persistent data storage for the testcase
TALER_TEST_HOME = test_exchange_api_keys_cherry_picking_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
-
-# 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
-
-[taler-exchange-secmod-rsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
+CURRENCY_ROUND_UNIT = EUR:0.01
[taler-exchange-secmod-eddsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
-DURATION = 14 days
-
-[auditor]
-BASE_URL = "http://localhost:8083/"
-
-# HTTP port the auditor listens to
-PORT = 8083
+OVERLAP_DURATION = 1 s
+DURATION = 30 s
+LOOKAHEAD_SIGN = 20 s
[exchange]
-# 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"
@@ -56,43 +27,32 @@ CONFIG = "postgres:///talercheck"
CONFIG = "postgres:///talercheck"
[exchange-account-1]
-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
[exchange-accountcredentials-1]
-WIRE_GATEWAY_URL = "http://localhost:9082/42/"
+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]
-PAYTO_URI = payto://x-taler-bank/localhost/2
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
ENABLE_DEBIT = YES
ENABLE_CREDIT = YES
[exchange-accountcredentials-2]
-WIRE_GATEWAY_URL = "http://localhost:9082/2/"
+WIRE_GATEWAY_URL = "http://localhost:9082/accounts/2/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
-# Authentication information for basic authentication
-TALER_BANK_AUTH_METHOD = "basic"
-USERNAME = user
-PASSWORD = pass
+[admin-accountcredentials-2]
+WIRE_GATEWAY_URL = "http://localhost:9082/accounts/2/taler-wire-gateway/"
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
[bank]
HTTP_PORT=8082
-
-[taler-exchange-secmod-rsa]
-OVERLAP_DURATION = 1 s
-LOOKAHEAD_SIGN = 20 s
-
-[taler-exchange-secmod-eddsa]
-OVERLAP_DURATION = 1 s
-DURATION = 30 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
-rsa_keysize = 1024
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/master.priv b/src/testing/test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-offline/master.priv
deleted file mode 100644
index 394926938..000000000
--- a/src/testing/test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-offline/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_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 1f8c2919a..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,12 +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;
/**
- * Exchange configuration data.
+ * Our credentials.
*/
-static struct TALER_TESTING_ExchangeConfiguration ec;
+static struct TALER_TESTING_Credentials cred;
/**
@@ -59,23 +60,28 @@ run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct TALER_TESTING_Command commands[] = {
- TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_wire_add ("add-wire-account",
- "payto://x-taler-bank/localhost/2",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
- CONFIG_FILE),
- TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
- 1),
- TALER_TESTING_cmd_check_keys ("first-download",
- 1),
- /* Causes GET /keys?last_denom_issue=0 */
- TALER_TESTING_cmd_check_keys_with_last_denom ("second-download",
- 1,
- "zero"),
+ 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 ()
};
@@ -90,41 +96,24 @@ main (int argc,
char *const *argv)
{
(void) argc;
- (void) 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))
{
- 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 0531c5b83..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,51 +58,55 @@ run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct TALER_TESTING_Command revocation[] = {
- TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_wire_add ("add-wire-account",
- "payto://x-taler-bank/localhost/2",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
- CONFIG_FILE),
- TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
- 1),
+ 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",
@@ -115,7 +115,7 @@ 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",
@@ -144,12 +144,12 @@ run (void *cls,
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,
@@ -211,12 +211,12 @@ 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_refresh ("recoup-2",
MHD_HTTP_OK,
@@ -238,9 +238,8 @@ run (void *cls,
};
(void) cls;
- TALER_TESTING_run_with_fakebank (is,
- revocation,
- bc.exchange_auth.wire_gateway_url);
+ TALER_TESTING_run (is,
+ revocation);
}
@@ -249,48 +248,24 @@ main (int argc,
char *const *argv)
{
(void) argc;
- (void) 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))
{
- 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 02471eb91..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,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"
@@ -40,22 +41,17 @@
* 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.
+ * Our credentials.
*/
-static char *twister_url;
+static struct TALER_TESTING_Credentials cred;
/**
- * Exchange configuration data.
- */
-static struct TALER_TESTING_ExchangeConfiguration ec;
-
-/**
- * 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.
@@ -72,8 +68,9 @@ static struct GNUNET_OS_Process *twisterd;
static struct TALER_TESTING_Command
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");
}
@@ -91,8 +88,8 @@ CMD_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);
}
@@ -111,8 +108,9 @@ run (void *cls,
* response from a refresh-reveal operation.
*/
struct TALER_TESTING_Command refresh_409_conflict[] = {
- CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve",
- "EUR:5.01"),
+ CMD_TRANSFER_TO_EXCHANGE (
+ "refresh-create-reserve",
+ "EUR:5.01"),
/**
* Make previous command effective.
*/
@@ -120,33 +118,38 @@ run (void *cls,
/**
* Withdraw EUR:5.
*/
- TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin",
- "refresh-create-reserve",
- "EUR:5",
- MHD_HTTP_OK),
- TALER_TESTING_cmd_deposit ("refresh-deposit-partial",
- "refresh-withdraw-coin",
- 0,
- bc.user42_payto,
- "{\"items\":[{\"name\":\"ice cream\",\
- \"value\":\"EUR:1\"}]}",
- GNUNET_TIME_UNIT_ZERO,
- "EUR:1",
- MHD_HTTP_OK),
+ 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",
+ "refresh-withdraw-coin",
+ 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 ("refresh-melt",
- "refresh-withdraw-coin",
- MHD_HTTP_OK,
- NULL),
+ 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,
- "transfer_privs.0"),
- TALER_TESTING_cmd_refresh_reveal ("refresh-(flipped-)reveal",
- "refresh-melt",
- MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_flip_upload (
+ "flip-upload",
+ config_file,
+ "transfer_privs.0"),
+ TALER_TESTING_cmd_refresh_reveal (
+ "refresh-(flipped-)reveal",
+ "refresh-melt",
+ MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_end ()
};
@@ -157,28 +160,31 @@ run (void *cls,
* lib test suite.
*/
struct TALER_TESTING_Command refund[] = {
- CMD_TRANSFER_TO_EXCHANGE ("create-reserve-r1",
- "EUR:5.01"),
+ CMD_TRANSFER_TO_EXCHANGE (
+ "create-reserve-r1",
+ "EUR:5.01"),
CMD_EXEC_WIREWATCH ("wirewatch-r1"),
- TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1",
- "create-reserve-r1",
- "EUR:5",
- MHD_HTTP_OK),
- TALER_TESTING_cmd_deposit ("deposit-refund-1",
- "withdraw-coin-r1",
- 0,
- bc.user42_payto,
- "{\"items\":[{\"name\":\"ice cream\","
- "\"value\":\"EUR:5\"}]}",
- GNUNET_TIME_UNIT_MINUTES,
- "EUR:5",
- MHD_HTTP_OK),
+ 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",
+ "withdraw-coin-r1",
+ 0,
+ 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_BAD_REQUEST,
"USD:5",
"deposit-refund-1"),
TALER_TESTING_cmd_flip_upload ("flip-upload",
- CONFIG_FILE,
+ config_file,
"merchant_sig"),
TALER_TESTING_cmd_refund ("refund-bad-sig",
MHD_HTTP_FORBIDDEN,
@@ -187,18 +193,18 @@ run (void *cls,
/* 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",
- "withdraw-coin-r1",
- 0, /* coin index. */
- bc.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\"}]}",
- GNUNET_TIME_UNIT_MINUTES,
- "EUR:5",
- MHD_HTTP_CONFLICT),
+ TALER_TESTING_cmd_deposit (
+ "deposit-refund-to-fail",
+ "withdraw-coin-r1",
+ 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\"}]}",
+ GNUNET_TIME_UNIT_MINUTES,
+ "EUR:5",
+ MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_refund ("refund-deposit-not-found",
MHD_HTTP_NOT_FOUND,
"EUR:5",
@@ -216,10 +222,11 @@ run (void *cls,
* 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_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),
@@ -229,27 +236,35 @@ run (void *cls,
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",
- MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount (
+ "withdraw-coin-r2",
+ "create-reserve-r2",
+ "EUR:5",
+ 0, /* age restriction off */
+ MHD_HTTP_OK),
TALER_TESTING_cmd_end ()
};
#endif
struct TALER_TESTING_Command commands[] = {
- TALER_TESTING_cmd_wire_add ("add-wire-account",
- "payto://x-taler-bank/localhost/2",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
- CONFIG_FILE),
- TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
- 1),
- 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),
@@ -258,16 +273,15 @@ run (void *cls,
};
(void) cls;
- TALER_TESTING_run_with_fakebank (is,
- commands,
- bc.exchange_auth.wire_gateway_url);
+ 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)
@@ -286,48 +300,34 @@ main (int argc,
int ret;
(void) argc;
- (void) argv;
- /* 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;
- 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,
- GNUNET_YES,
- &ec))
{
- 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);
+ char *cipher;
- if (GNUNET_OK != ret)
- return 1;
- break;
- default:
- GNUNET_break (0);
- return 1;
+ 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 a41cfd43c..000000000
--- a/src/testing/test_exchange_api_twisted.conf
+++ /dev/null
@@ -1,150 +0,0 @@
-# This file is in the public domain.
-[PATHS]
-# Persistent data storage for the testcase
-TALER_TEST_HOME = test_exchange_api_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
-
-[taler-exchange-secmod-rsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-
-[taler-exchange-secmod-eddsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
-DURATION = 14 days
-
-[taler]
-# Currency supported by the exchange (can only be one)
-CURRENCY = EUR
-CURRENCY_ROUND_UNIT = EUR:0.01
-
-[exchange]
-# 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"
-
-[exchange-accountcredentials-1]
-WIRE_GATEWAY_URL = "http://localhost:9081/42/"
-WIRE_GATEWAY_AUTH_METHOD = NONE
-
-[exchange-account-2]
-PAYTO_URI = payto://x-taler-bank/localhost/2
-ENABLE_DEBIT = YES
-ENABLE_CREDIT = YES
-
-[exchange-accountcredentials-2]
-WIRE_GATEWAY_URL = "http://localhost:8082/2/"
-WIRE_GATEWAY_AUTH_METHOD = BASIC
-USERNAME = user
-PASSWORD = pass
-
-[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
-
-
-[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
index b2d7871ea..7cce61b55 100644
--- a/src/testing/test_exchange_management_api.c
+++ b/src/testing/test_exchange_management_api.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -25,6 +25,7 @@
#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"
@@ -32,18 +33,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;
/**
@@ -57,10 +52,21 @@ run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct TALER_TESTING_Command commands[] = {
- /* this currently fails, because the
- auditor is already added by the test setup logic */
- TALER_TESTING_cmd_auditor_del ("del-auditor-NOT-FOUND",
- MHD_HTTP_NOT_FOUND,
+ 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,
@@ -111,44 +117,51 @@ run (void *cls,
MHD_HTTP_NO_CONTENT,
false),
TALER_TESTING_cmd_wire_add ("add-wire-account",
- "payto://x-taler-bank/localhost/42",
+ "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",
+ "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",
+ "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",
+ "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",
+ "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",
+ "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",
+ "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_check_keys_pull_all_keys ("refetch /keys",
- 1),
+ 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_with_fakebank (is,
- commands,
- bc.exchange_auth.wire_gateway_url);
+ TALER_TESTING_run (is,
+ commands);
}
@@ -157,48 +170,24 @@ main (int argc,
char *const *argv)
{
(void) argc;
- (void) argv;
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup ("test-exchange-management-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. 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, /* reset DB? */
- &ec))
{
- 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_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
index b9e9a9f83..0844c5818 100644
--- a/src/testing/test_kyc_api.c
+++ b/src/testing/test_kyc_api.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 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
@@ -23,6 +23,7 @@
*/
#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"
@@ -40,14 +41,10 @@
#define CONFIG_FILE "test_kyc_api.conf"
/**
- * Exchange configuration data.
+ * Our credentials.
*/
-static struct TALER_TESTING_ExchangeConfiguration ec;
+struct TALER_TESTING_Credentials cred;
-/**
- * Bank configuration data.
- */
-static struct TALER_TESTING_BankConfiguration bc;
/**
* Execute the taler-exchange-wirewatch command with
@@ -56,7 +53,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
@@ -78,8 +75,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
@@ -91,24 +88,25 @@ static void
run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
- /**
- * Test withdraw.
- */
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", bc.user42_payto, bc.exchange_payto,
+ "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",
- MHD_HTTP_ACCEPTED),
+ 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 ()
};
@@ -120,16 +118,27 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-lacking-kyc",
"create-reserve-1",
"EUR:5",
- MHD_HTTP_ACCEPTED),
- TALER_TESTING_cmd_proof_kyc ("proof-kyc",
- "withdraw-coin-1-lacking-kyc",
- "pass",
- "state",
- MHD_HTTP_SEE_OTHER),
+ 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[] = {
@@ -137,7 +146,7 @@ run (void *cls,
"deposit-simple",
"withdraw-coin-1",
0,
- bc.user43_payto,
+ cred.user43_payto,
"{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
GNUNET_TIME_UNIT_ZERO,
"EUR:5",
@@ -154,70 +163,378 @@ run (void *cls,
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",
+ "track-deposit-kyc-ready",
MHD_HTTP_ACCEPTED),
- TALER_TESTING_cmd_proof_kyc ("proof-kyc-no-service",
- "track-deposit",
- "bad",
- "state",
- MHD_HTTP_BAD_GATEWAY),
+ 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 ("proof-kyc-fail",
- "track-deposit",
- "bad",
- "state",
- MHD_HTTP_FORBIDDEN),
- TALER_TESTING_cmd_proof_kyc ("proof-kyc-fail",
- "track-deposit",
- "pass",
- "state",
- MHD_HTTP_SEE_OTHER),
+ 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",
- ec.exchange_url,
+ cred.exchange_url,
"EUR:4.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 ()
};
struct TALER_TESTING_Command wallet_kyc[] = {
- TALER_TESTING_cmd_wallet_kyc_get (
- "wallet-kyc-fail",
- NULL,
+ 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_proof_kyc ("proof-wallet-kyc",
- "wallet-kyc-fail",
- "pass",
- "state",
- MHD_HTTP_SEE_OTHER),
- TALER_TESTING_cmd_check_kyc_get (
- "wallet-kyc-check",
- "wallet-kyc-fail",
+ 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_exec_offline_sign_fees ("offline-sign-fees",
- CONFIG_FILE,
- "EUR:0.01",
- "EUR:0.01"),
- TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_wire_add ("add-wire-account",
- "payto://x-taler-bank/localhost/2",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
- CONFIG_FILE),
- TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
- 2),
+ 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",
@@ -228,13 +545,20 @@ run (void *cls,
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_with_fakebank (is,
- commands,
- bc.exchange_auth.wire_gateway_url);
+ TALER_TESTING_run (is,
+ commands);
}
@@ -243,48 +567,14 @@ main (int argc,
char *const *argv)
{
(void) argc;
- (void) argv;
- /* These environment variables get in the way... */
- unsetenv ("XDG_DATA_HOME");
- unsetenv ("XDG_CONFIG_HOME");
- GNUNET_log_setup ("test-kyc-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))
- {
- 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;
+ return TALER_TESTING_main (argv,
+ "INFO",
+ CONFIG_FILE,
+ "exchange-account-2",
+ TALER_TESTING_BS_FAKEBANK,
+ &cred,
+ &run,
+ NULL);
}
diff --git a/src/testing/test_kyc_api.conf b/src/testing/test_kyc_api.conf
index 8ca6b74b8..b6bfdb055 100644
--- a/src/testing/test_kyc_api.conf
+++ b/src/testing/test_kyc_api.conf
@@ -1,157 +1,42 @@
-
# This file is in the public domain.
#
-[PATHS]
-# Persistent data storage for the testcase
-TALER_TEST_HOME = test_exchange_api_home/
-TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
-
-[taler-exchange-secmod-rsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-
-[taler-exchange-secmod-eddsa]
-# Reduce from 1 year to speed up test
-LOOKAHEAD_SIGN = 24 days
-# Reduce from 12 weeks to ensure we have multiple
-DURATION = 14 days
-
-
-[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]
-
-# 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/"
-
-
-KYC_MODE = OAUTH2
-
-KYC_WALLET_BALANCE_LIMIT = EUR:1
-
-KYC_WITHDRAW_PERIOD = "31 days"
-
-KYC_WITHDRAW_LIMIT = EUR:8
-
-[exchange-kyc-oauth2]
-
-KYC_OAUTH2_URL = http://localhost:6666/oauth/v2/login
-KYC_INFO_URL = http://localhost:6666/api/user/me
+@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/
-
-[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.
-[exchange-account-1]
-# What is the URL of our account?
-PAYTO_URI = "payto://x-taler-bank/localhost/42"
-
-[exchange-accountcredentials-1]
-WIRE_GATEWAY_URL = "http://localhost:8082/42/"
-
-[bank]
-HTTP_PORT = 8082
-
-# ENABLE_CREDIT = YES
-
-[exchange-account-2]
-# What is the bank account (with the "Taler Bank" demo system)?
-PAYTO_URI = "payto://x-taler-bank/localhost/2"
-ENABLE_DEBIT = YES
-ENABLE_CREDIT = YES
-
-# Authentication information for basic authentication
-[exchange-accountcredentials-2]
-WIRE_GATEWAY_URL = "http://localhost:8082/2/"
-WIRE_GATEWAY_AUTH_METHOD = "basic"
-USERNAME = user
-PASSWORD = pass
-
-
-
-
-# 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
+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 bf28375b2..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.
- */
-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,11 +63,13 @@ run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct TALER_TESTING_Command all[] = {
- TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
- config_filename,
- "EUR:0.01",
- "EUR:0.01"),
- // 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"),
@@ -108,7 +77,7 @@ 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 (),
@@ -119,15 +88,15 @@ 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 (),
@@ -136,7 +105,7 @@ run (void *cls,
"EUR:0.1"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-2b",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
GNUNET_TIME_timestamp_get (),
@@ -148,15 +117,15 @@ 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 (),
@@ -164,7 +133,7 @@ run (void *cls,
"EUR:1",
"EUR:0.1"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-3b",
- &dbc,
+ cred.cfg,
"bob",
"5",
GNUNET_TIME_timestamp_get (),
@@ -172,7 +141,7 @@ run (void *cls,
"EUR:1",
"EUR:0.1"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-3c",
- &dbc,
+ cred.cfg,
"alice",
"4",
GNUNET_TIME_timestamp_get (),
@@ -183,25 +152,25 @@ 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 (),
@@ -211,7 +180,7 @@ run (void *cls,
"EUR:0.2",
"EUR:0.1"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-4b",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
GNUNET_TIME_timestamp_get (),
@@ -230,14 +199,14 @@ 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 (),
@@ -248,7 +217,7 @@ run (void *cls,
"EUR:0.1"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-5b",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
GNUNET_TIME_timestamp_get (),
@@ -267,13 +236,13 @@ 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 (),
@@ -285,7 +254,7 @@ 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 (),
@@ -293,7 +262,7 @@ run (void *cls,
"EUR:0.102",
"EUR:0.1"),
TALER_TESTING_cmd_insert_deposit ("do-deposit-6c",
- &dbc,
+ cred.cfg,
"bob",
USER42_ACCOUNT,
GNUNET_TIME_timestamp_get (),
@@ -305,7 +274,7 @@ 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 (),
@@ -317,7 +286,7 @@ 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 (),
@@ -327,14 +296,14 @@ run (void *cls,
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 (),
@@ -346,7 +315,7 @@ 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 (),
@@ -356,14 +325,14 @@ run (void *cls,
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 (),
@@ -373,14 +342,14 @@ run (void *cls,
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 (),
@@ -394,7 +363,7 @@ 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 (),
@@ -410,7 +379,7 @@ 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 (),
@@ -420,14 +389,14 @@ run (void *cls,
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 (),
@@ -441,7 +410,7 @@ 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 (),
@@ -457,7 +426,7 @@ 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 (),
@@ -468,49 +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 ()
};
- TALER_TESTING_run_with_fakebank (is,
- all,
- bc.exchange_auth.wire_gateway_url);
-}
-
-
-/**
- * Prepare database and launch the test.
- *
- * @param cls unused
- * @param is interpreter to use
- */
-static void
-prepare_database (void *cls,
- struct TALER_TESTING_Interpreter *is)
-{
- dbc.plugin = TALER_EXCHANGEDB_plugin_load (is->cfg);
- if (NULL == dbc.plugin)
- {
- GNUNET_break (0);
- result = 77;
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- if (GNUNET_OK !=
- dbc.plugin->preflight (dbc.plugin->cls))
- {
- GNUNET_break (0);
- result = 77;
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- GNUNET_SCHEDULER_add_shutdown (&unload_db,
- NULL);
- run (NULL,
- is);
+ TALER_TESTING_run (is,
+ all);
}
@@ -519,7 +454,6 @@ main (int argc,
char *const argv[])
{
const char *plugin_name;
- char *testname;
if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
{
@@ -527,52 +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;
- }
- result = GNUNET_OK;
- if (GNUNET_OK !=
- TALER_TESTING_setup_with_exchange (&prepare_database,
- NULL,
- config_filename))
- {
- 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/master.priv b/src/testing/test_taler_exchange_httpd_home/.local/share/taler/exchange-offline/master.priv
deleted file mode 100644
index 394926938..000000000
--- a/src/testing/test_taler_exchange_httpd_home/.local/share/taler/exchange-offline/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_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 30c2cf27f..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,21 +77,19 @@ run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct TALER_TESTING_Command all[] = {
- TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
- config_filename,
- "EUR:0.01",
- "EUR:0.01"),
- TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_wire_add ("add-wire-account",
- "payto://x-taler-bank/localhost/2",
- MHD_HTTP_NO_CONTENT,
- false),
- TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys",
- config_filename),
- TALER_TESTING_cmd_check_keys_pull_all_keys ("refetch /keys",
- 1),
+ 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",
@@ -111,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",
@@ -135,18 +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 ()
};
(void) cls;
- TALER_TESTING_run_with_fakebank (is,
- all,
- bc.exchange_auth.wire_gateway_url);
+ TALER_TESTING_run (is,
+ all);
}
@@ -154,69 +146,29 @@ int
main (int argc,
char *const argv[])
{
- const char *plugin_name;
-
(void) argc;
- /* 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) '-')))
{
- 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
index 8362b66c9..ac9c2c8fb 100644
--- a/src/testing/testing_api_cmd_auditor_add.c
+++ b/src/testing/testing_api_cmd_auditor_add.c
@@ -18,7 +18,7 @@
*/
/**
* @file testing/testing_api_cmd_auditor_add.c
- * @brief command for testing /auditor_add.
+ * @brief command for testing auditor_add
* @author Christian Grothoff
*/
#include "platform.h"
@@ -62,27 +62,22 @@ struct AuditorAddState
* if the response code is acceptable.
*
* @param cls closure.
- * @param hr HTTP response details
+ * @param aer response details
*/
static void
-auditor_add_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+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)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- hr->http_status,
- ds->is->commands[ds->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- stderr,
- 0);
- TALER_TESTING_interpreter_fail (ds->is);
+ TALER_TESTING_unexpected_status (ds->is,
+ hr->http_status,
+ ds->expected_response_code);
return;
}
TALER_TESTING_interpreter_next (ds->is);
@@ -104,10 +99,43 @@ auditor_add_run (void *cls,
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,
@@ -116,17 +144,22 @@ auditor_add_run (void *cls,
}
else
{
- TALER_exchange_offline_auditor_add_sign (&is->auditor_pub,
- is->auditor_url,
+ 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,
- &is->master_priv,
+ master_priv,
&master_sig);
}
ds->dh = TALER_EXCHANGE_management_enable_auditor (
- is->ctx,
- is->exchange_url,
- &is->auditor_pub,
- is->auditor_url,
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ auditor_pub,
+ auditor_url,
"test-case auditor", /* human-readable auditor name */
now,
&master_sig,
@@ -156,10 +189,8 @@ auditor_add_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_TESTING_command_incomplete (ds->is,
+ cmd->label);
TALER_EXCHANGE_management_enable_auditor_cancel (ds->dh);
ds->dh = NULL;
}
diff --git a/src/testing/testing_api_cmd_auditor_add_denom_sig.c b/src/testing/testing_api_cmd_auditor_add_denom_sig.c
index 33cd9575e..6b7776896 100644
--- a/src/testing/testing_api_cmd_auditor_add_denom_sig.c
+++ b/src/testing/testing_api_cmd_auditor_add_denom_sig.c
@@ -67,27 +67,22 @@ struct AuditorAddDenomSigState
* if the response code is acceptable.
*
* @param cls closure.
- * @param hr HTTP response details
+ * @param adr response details
*/
static void
-denom_sig_add_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+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)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- hr->http_status,
- ds->is->commands[ds->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- stderr,
- 0);
- TALER_TESTING_interpreter_fail (ds->is);
+ TALER_TESTING_unexpected_status (ds->is,
+ hr->http_status,
+ ds->expected_response_code);
return;
}
TALER_TESTING_interpreter_next (ds->is);
@@ -108,8 +103,13 @@ auditor_add_run (void *cls,
{
struct AuditorAddDenomSigState *ds = cls;
struct TALER_AuditorSignatureP auditor_sig;
- struct TALER_DenominationHash h_denom_pub;
+ 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 */
@@ -118,7 +118,6 @@ auditor_add_run (void *cls,
denom_cmd = TALER_TESTING_interpreter_lookup_command (is,
ds->denom_ref);
-
if (NULL == denom_cmd)
{
GNUNET_break (0);
@@ -131,6 +130,31 @@ auditor_add_run (void *cls,
&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,
@@ -139,31 +163,33 @@ auditor_add_run (void *cls,
}
else
{
- struct TALER_MasterPublicKeyP master_pub;
+ const struct TALER_MasterPublicKeyP *master_pub;
+ const struct TALER_AuditorPrivateKeyP *auditor_priv;
- GNUNET_CRYPTO_eddsa_key_get_public (&is->master_priv.eddsa_priv,
- &master_pub.eddsa_pub);
+ 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 (
- is->auditor_url,
+ auditor_url,
&dk->h_key,
- &master_pub,
+ master_pub,
dk->valid_from,
dk->withdraw_valid_until,
dk->expire_deposit,
dk->expire_legal,
&dk->value,
- &dk->fee_withdraw,
- &dk->fee_deposit,
- &dk->fee_refresh,
- &dk->fee_refund,
- &is->auditor_priv,
+ &dk->fees,
+ auditor_priv,
&auditor_sig);
}
ds->dh = TALER_EXCHANGE_add_auditor_denomination (
- is->ctx,
- is->exchange_url,
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
&h_denom_pub,
- &is->auditor_pub,
+ auditor_pub,
&auditor_sig,
&denom_sig_add_cb,
ds);
@@ -191,10 +217,8 @@ auditor_add_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_TESTING_command_incomplete (ds->is,
+ cmd->label);
TALER_EXCHANGE_add_auditor_denomination_cancel (ds->dh);
ds->dh = NULL;
}
diff --git a/src/testing/testing_api_cmd_auditor_del.c b/src/testing/testing_api_cmd_auditor_del.c
index de03d1632..8256bc1c7 100644
--- a/src/testing/testing_api_cmd_auditor_del.c
+++ b/src/testing/testing_api_cmd_auditor_del.c
@@ -62,27 +62,23 @@ struct AuditorDelState
* if the response code is acceptable.
*
* @param cls closure.
- * @param hr HTTP response details
+ * @param adr response details
*/
static void
-auditor_del_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+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)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- hr->http_status,
- ds->is->commands[ds->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- stderr,
- 0);
- TALER_TESTING_interpreter_fail (ds->is);
+ TALER_TESTING_unexpected_status (ds->is,
+ hr->http_status,
+ ds->expected_response_code);
return;
}
TALER_TESTING_interpreter_next (ds->is);
@@ -104,10 +100,36 @@ auditor_del_run (void *cls,
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,
@@ -116,15 +138,20 @@ auditor_del_run (void *cls,
}
else
{
- TALER_exchange_offline_auditor_del_sign (&is->auditor_pub,
+ 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,
- &is->master_priv,
+ master_priv,
&master_sig);
}
ds->dh = TALER_EXCHANGE_management_disable_auditor (
- is->ctx,
- is->exchange_url,
- &is->auditor_pub,
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
+ auditor_pub,
now,
&master_sig,
&auditor_del_cb,
@@ -153,10 +180,8 @@ auditor_del_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_TESTING_command_incomplete (ds->is,
+ cmd->label);
TALER_EXCHANGE_management_disable_auditor_cancel (ds->dh);
ds->dh = NULL;
}
diff --git a/src/testing/testing_api_cmd_auditor_deposit_confirmation.c b/src/testing/testing_api_cmd_auditor_deposit_confirmation.c
index debf343cc..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, 2021 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,13 +132,15 @@ do_retry (void *cls)
* to check if the response code is acceptable.
*
* @param cls closure.
- * @param hr HTTP response details
+ * @param dcr response details
*/
static void
-deposit_confirmation_cb (void *cls,
- const struct TALER_AUDITOR_HttpResponse *hr)
+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 != hr->http_status)
@@ -166,21 +162,16 @@ deposit_confirmation_cb (void *cls,
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",
- hr->http_status,
- dcs->is->commands[dcs->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply, 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);
@@ -199,31 +190,54 @@ deposit_confirmation_run (void *cls,
const struct TALER_TESTING_Command *cmd,
struct TALER_TESTING_Interpreter *is)
{
- static struct TALER_ExtensionContractHash no_h_extensions;
+ static struct TALER_ExtensionPolicyHashP no_h_policy;
struct DepositConfirmationState *dcs = cls;
const struct TALER_TESTING_Command *deposit_cmd;
- struct TALER_MerchantWireHash h_wire;
- struct TALER_PrivateContractHash h_contract_terms;
+ 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,22 +250,22 @@ 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));
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_timestamp (deposit_cmd,
- dcs->coin_index,
+ 0,
&exchange_timestamp));
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_wire_deadline (deposit_cmd,
- dcs->coin_index,
+ 0,
&wire_deadline));
GNUNET_assert (NULL != exchange_timestamp);
- keys = TALER_EXCHANGE_get_keys (dcs->is->exchange);
+ keys = TALER_TESTING_get_keys (is);
GNUNET_assert (NULL != keys);
spk = TALER_EXCHANGE_get_signing_key_info (keys,
exchange_pub);
@@ -270,12 +284,23 @@ deposit_confirmation_run (void *cls,
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,
&merchant_priv));
@@ -291,7 +316,8 @@ deposit_confirmation_run (void *cls,
&timestamp),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("refund_deadline",
- &refund_deadline)),
+ &refund_deadline),
+ NULL),
GNUNET_JSON_spec_end ()
};
@@ -307,25 +333,29 @@ deposit_confirmation_run (void *cls,
if (GNUNET_TIME_absolute_is_zero (refund_deadline.abs_time))
refund_deadline = timestamp;
}
- dcs->dc = TALER_AUDITOR_deposit_confirmation (dcs->auditor,
- &h_wire,
- &no_h_extensions,
- &h_contract_terms,
- *exchange_timestamp,
- *wire_deadline,
- 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)
{
@@ -352,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,18 +398,16 @@ deposit_confirmation_cleanup (void *cls,
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;
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 1e412b2d1..000000000
--- a/src/testing/testing_api_cmd_auditor_exchanges.c
+++ /dev/null
@@ -1,380 +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 hr HTTP response details
- * @param num_exchanges length of the @a ei array
- * @param ei array with information about the exchanges
- */
-static void
-exchanges_cb (void *cls,
- const struct TALER_AUDITOR_HttpResponse *hr,
- unsigned int num_exchanges,
- const struct TALER_AUDITOR_ExchangeInfo *ei)
-{
- struct ExchangesState *es = cls;
-
- es->leh = NULL;
- if (es->expected_response_code != hr->http_status)
- {
- if (0 != es->do_retry)
- {
- es->do_retry--;
- 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 list exchanges failed with %u/%d\n",
- hr->http_status,
- (int) hr->ec);
- /* on DB conflicts, do not use backoff */
- if (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->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/%d to command %s in %s:%u\n",
- hr->http_status,
- (int) hr->ec,
- es->is->commands[es->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- 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 8ec128c01..588be43d8 100644
--- a/src/testing/testing_api_cmd_auditor_exec_auditor.c
+++ b/src/testing/testing_api_cmd_auditor_exec_auditor.c
@@ -68,6 +68,7 @@ auditor_run (void *cls,
"taler-auditor",
"taler-auditor",
"-c", ks->config_filename,
+ "-I",
NULL);
if (NULL == ks->auditor_proc)
{
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 64e010efa..5c031d0b3 100644
--- a/src/testing/testing_api_cmd_bank_admin_add_incoming.c
+++ b/src/testing/testing_api_cmd_bank_admin_add_incoming.c
@@ -107,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.
@@ -180,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);
@@ -194,40 +193,54 @@ 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_Timestamp 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->reserve_history.details.in_details.timestamp = timestamp;
- fts->reserve_history.details.in_details.wire_reference = serial_id;
fts->aih = NULL;
- if (http_status != fts->expected_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)
{
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
+ TALER_TESTING_interpreter_next (is);
return;
}
- switch (http_status)
+ 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->serial_id = serial_id;
- fts->timestamp = timestamp;
+ 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:
@@ -251,22 +264,22 @@ confirmation_cb (void *cls,
if (0 != fts->do_retry)
{
fts->do_retry--;
- if ( (0 == http_status) ||
- (TALER_EC_GENERIC_DB_SOFT_FAILURE == 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,
"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_GENERIC_DB_SOFT_FAILURE == 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++;
+ TALER_TESTING_inc_tries (fts->is);
fts->retry_task = GNUNET_SCHEDULER_add_delayed (
fts->backoff,
&do_retry,
@@ -279,8 +292,8 @@ confirmation_cb (void *cls,
GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Fakebank returned HTTP status %u/%d\n",
- http_status,
- (int) ec);
+ air->http_status,
+ (int) air->ec);
TALER_TESTING_interpreter_fail (is);
}
@@ -301,6 +314,7 @@ admin_add_incoming_run (void *cls,
bool have_public = false;
(void) cmd;
+ fts->is = is;
/* Use reserve public key as subject */
if (NULL != fts->reserve_reference)
{
@@ -351,7 +365,6 @@ admin_add_incoming_run (void *cls,
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),
@@ -385,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;
}
@@ -426,16 +438,19 @@ admin_add_incoming_traits (void *cls,
{
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_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_credit_payto_uri (void_uri),
TALER_TESTING_make_trait_exchange_bank_account_url (
- &fts->exchange_credit_url),
+ fts->exchange_credit_url),
TALER_TESTING_make_trait_amount (&fts->amount),
- TALER_TESTING_make_trait_timestamp (0, &fts->timestamp),
+ 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 (&fts->reserve_history),
+ TALER_TESTING_make_trait_reserve_history (0,
+ &fts->reserve_history),
TALER_TESTING_trait_end ()
};
@@ -448,15 +463,17 @@ admin_add_incoming_traits (void *cls,
{
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_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_credit_payto_uri (void_uri),
TALER_TESTING_make_trait_exchange_bank_account_url (
- &fts->exchange_credit_url),
+ fts->exchange_credit_url),
TALER_TESTING_make_trait_amount (&fts->amount),
- TALER_TESTING_make_trait_timestamp (0, &fts->timestamp),
+ TALER_TESTING_make_trait_timestamp (0,
+ &fts->timestamp),
TALER_TESTING_make_trait_reserve_pub (&fts->reserve_pub),
- TALER_TESTING_make_trait_reserve_history (&fts->reserve_history),
+ TALER_TESTING_make_trait_reserve_history (0,
+ &fts->reserve_history),
TALER_TESTING_trait_end ()
};
diff --git a/src/testing/testing_api_cmd_bank_admin_check.c b/src/testing/testing_api_cmd_bank_admin_check.c
index 21a230834..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);
@@ -102,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 !=
@@ -110,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;
}
@@ -123,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,
diff --git a/src/testing/testing_api_cmd_bank_check.c b/src/testing/testing_api_cmd_bank_check.c
index ed4cde461..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
@@ -91,26 +91,48 @@ check_bank_transfer_run (void *cls,
struct TALER_Amount amount;
char *debit_account;
char *credit_account;
- const char **exchange_base_url;
- const char **debit_payto;
- const char **credit_payto;
+ 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");
- debit_payto = &bcs->debit_payto;
- credit_payto = &bcs->credit_payto;
- exchange_base_url = &bcs->exchange_base_url;
+ debit_payto = bcs->debit_payto;
+ credit_payto = bcs->credit_payto;
+ exchange_base_url = bcs->exchange_base_url;
if (GNUNET_OK !=
TALER_string_to_amount (bcs->amount,
&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;
}
@@ -145,27 +167,22 @@ check_bank_transfer_run (void *cls,
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);
-
+ 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_payto,
debit_account);
-
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"converted credit_payto (%s) to credit_account (%s)\n",
- *credit_payto,
+ credit_payto,
credit_account);
-
if (GNUNET_OK !=
- TALER_FAKEBANK_check_debit (is->fakebank,
+ TALER_FAKEBANK_check_debit (fakebank,
&amount,
debit_account,
credit_account,
- *exchange_base_url,
+ exchange_base_url,
&bcs->wtid))
{
GNUNET_break (0);
@@ -217,7 +234,7 @@ check_bank_transfer_traits (void *cls,
struct TALER_TESTING_Trait traits[] = {
TALER_TESTING_make_trait_wtid (wtid_ptr),
TALER_TESTING_make_trait_exchange_url (
- &bcs->exchange_base_url),
+ bcs->exchange_base_url),
TALER_TESTING_trait_end ()
};
@@ -228,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,
@@ -270,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 a7ad40a43..956e6c857 100644
--- a/src/testing/testing_api_cmd_bank_history_credit.c
+++ b/src/testing/testing_api_cmd_bank_history_credit.c
@@ -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;
@@ -144,38 +149,161 @@ print_expected (struct History *h,
/**
+ * 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);
@@ -185,118 +313,91 @@ build_history (struct TALER_TESTING_Interpreter *is,
GNUNET_assert (NULL != add_incoming_cmd);
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_row (add_incoming_cmd,
- &row_id_start));
+ &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_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)) )
- 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_uri = 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_uri = *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;
}
@@ -311,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,
@@ -329,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_uri,
- details->debit_account_uri)) )
+ (0 != strcasecmp (u1,
+ u2)) )
{
GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "expected debit_account_uri: %s\n",
- details->debit_account_uri);
+ "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_uri: %s\n",
- h[off].details.debit_account_uri);
+ "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;
}
@@ -360,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 enum GNUNET_GenericReturnValue
+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) ||
- (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 ? 1 : 0);
- 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;
+ 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 = true;
- 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,
+ chr->http_status,
+ (int) chr->ec,
+ hs->failed ? 1 : 0);
+ print_expected (hs->h,
+ hs->total,
+ UINT_MAX);
+ TALER_TESTING_interpreter_fail (is);
}
@@ -463,6 +581,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)
{
@@ -483,15 +602,16 @@ history_run (void *cls,
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,
- GNUNET_TIME_UNIT_ZERO,
- &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);
}
@@ -512,7 +632,8 @@ 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);
diff --git a/src/testing/testing_api_cmd_bank_history_debit.c b/src/testing/testing_api_cmd_bank_history_debit.c
index a1dee81e1..1cb7320fa 100644
--- a/src/testing/testing_api_cmd_bank_history_debit.c
+++ b/src/testing/testing_api_cmd_bank_history_debit.c
@@ -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;
@@ -147,38 +152,155 @@ print_expected (struct History *h,
/**
+ * 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++;
+}
+
+
+/**
* 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 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 (
@@ -187,124 +309,91 @@ build_history (struct TALER_TESTING_Interpreter *is,
GNUNET_assert (NULL != add_incoming_cmd);
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_row (add_incoming_cmd,
- &row_id_start));
+ &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_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)) )
- 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_uri = h[pos].c_url;
- h[pos].details.debit_account_uri = 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;
}
- 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;
+ 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;
+ }
+ return npt;
}
@@ -325,6 +414,9 @@ check_result (struct History *h,
unsigned int off,
const struct TALER_BANK_DebitDetails *details)
{
+ char *u1;
+ char *u2;
+
if (off >= total)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -337,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_uri,
- details->credit_account_uri)) )
+ (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);
@@ -362,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 enum GNUNET_GenericReturnValue
+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);
}
@@ -465,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)
{
@@ -485,14 +594,16 @@ history_run (void *cls,
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,
- GNUNET_TIME_UNIT_ZERO,
- &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);
}
@@ -513,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++)
diff --git a/src/testing/testing_api_cmd_bank_transfer.c b/src/testing/testing_api_cmd_bank_transfer.c
index 8c14aac19..bfb29e120 100644
--- a/src/testing/testing_api_cmd_bank_transfer.c
+++ b/src/testing/testing_api_cmd_bank_transfer.c
@@ -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_Timestamp 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_GENERIC_DB_SOFT_FAILURE == 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_GENERIC_DB_SOFT_FAILURE == 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,
- "Bank 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);
}
@@ -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;
}
@@ -319,12 +306,12 @@ transfer_traits (void *cls,
struct TransferState *fts = cls;
struct TALER_TESTING_Trait traits[] = {
TALER_TESTING_make_trait_exchange_url (
- (const char **) &fts->exchange_base_url),
+ fts->exchange_base_url),
TALER_TESTING_make_trait_bank_row (&fts->serial_id),
TALER_TESTING_make_trait_credit_payto_uri (
- (const char **) &fts->payto_credit_account),
+ fts->payto_credit_account),
TALER_TESTING_make_trait_debit_payto_uri (
- (const char **) &fts->payto_debit_account),
+ 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),
diff --git a/src/testing/testing_api_cmd_batch.c b/src/testing/testing_api_cmd_batch.c
index e8f76ca37..5bb7b974e 100644
--- a/src/testing/testing_api_cmd_batch.c
+++ b/src/testing/testing_api_cmd_batch.c
@@ -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,8 +103,9 @@ 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]);
+ if (NULL != bs->batch[i].cleanup)
+ bs->batch[i].cleanup (bs->batch[i].cls,
+ &bs->batch[i]);
GNUNET_free (bs->batch);
GNUNET_free (bs);
}
@@ -121,7 +128,7 @@ batch_traits (void *cls,
{
struct BatchState *bs = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_batch_cmds (&bs->batch),
+ TALER_TESTING_make_trait_batch_cmds (bs->batch),
TALER_TESTING_trait_end ()
};
@@ -149,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,
@@ -166,19 +173,33 @@ TALER_TESTING_cmd_batch (const char *label,
}
-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;
+ }
+ 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;
+ }
}
- bs->batch[bs->batch_ip].finish_time = GNUNET_TIME_absolute_get ();
+ /* Simple command is done */
+ bcmd->finish_time = GNUNET_TIME_absolute_get ();
bs->batch_ip++;
+ return false;
}
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_change_auth.c b/src/testing/testing_api_cmd_change_auth.c
deleted file mode 100644
index 588eaa730..000000000
--- a/src/testing/testing_api_cmd_change_auth.c
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- 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 General Public License as
- published by the Free Software Foundation; either version 3, or
- (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received 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_change_auth.c
- * @brief command(s) to change CURL context authorization header
- * @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 "authchange" CMD.
- */
-struct AuthchangeState
-{
-
- /**
- * What is the new authorization token to send?
- */
- const char *auth_token;
-
- /**
- * Old context, clean up on termination.
- */
- struct GNUNET_CURL_Context *old_ctx;
-};
-
-
-/**
- * Run the command.
- *
- * @param cls closure.
- * @param cmd the command to execute.
- * @param is the interpreter state.
- */
-static void
-authchange_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct AuthchangeState *ss = cls;
-
- (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);
-}
-
-
-/**
- * Cleanup the state from a "authchange" CMD.
- *
- * @param cls closure.
- * @param cmd the command which is being cleaned up.
- */
-static void
-authchange_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct AuthchangeState *ss = cls;
-
- (void) cmd;
- if (NULL != ss->old_ctx)
- {
- GNUNET_CURL_fini (ss->old_ctx);
- ss->old_ctx = NULL;
- }
- GNUNET_free (ss);
-}
-
-
-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;
- }
-}
-
-
-/* end of testing_api_cmd_change_auth.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 0dee8be3a..000000000
--- a/src/testing/testing_api_cmd_check_keys.c
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- This file is part of TALER
- (C) 2018, 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 testing/testing_api_cmd_check_keys.c
- * @brief Implementation of "check keys" test command.
- * @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"
-
-
-/**
- * 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;
-
- /**
- * If this value is true, then the "cherry
- * picking" facility is turned off; whole /keys is
- * downloaded.
- */
- bool pull_all_keys;
-
- /**
- * Label of a command to use to derive the "last_denom_issue" date to use.
- */
- const char *last_denom_date_ref;
-
- /**
- * Last denomination date we received when doing this request.
- */
- struct GNUNET_TIME_Timestamp my_denom_date;
-};
-
-
-/**
- * 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)
- {
- struct GNUNET_TIME_Timestamp rdate;
-
- is->working = GNUNET_NO;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Triggering GET /keys, cmd `%s'\n",
- cmd->label);
- if (NULL != cks->last_denom_date_ref)
- {
- if (0 == strcmp ("zero",
- cks->last_denom_date_ref))
- {
- TALER_LOG_DEBUG ("Forcing last_denom_date URL argument set to zero\n");
- TALER_EXCHANGE_set_last_denom (is->exchange,
- GNUNET_TIME_UNIT_ZERO_TS);
- }
- else
- {
- const struct GNUNET_TIME_Timestamp *last_denom_date;
- const struct TALER_TESTING_Command *ref;
-
- ref = TALER_TESTING_interpreter_lookup_command (is,
- cks->last_denom_date_ref);
- if (NULL == ref)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_timestamp (ref,
- 0,
- &last_denom_date))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
- TALER_LOG_DEBUG ("Forcing last_denom_date URL argument\n");
- TALER_EXCHANGE_set_last_denom (is->exchange,
- *last_denom_date);
- }
- }
-
- rdate = TALER_EXCHANGE_check_keys_current (
- is->exchange,
- cks->pull_all_keys
- ? TALER_EXCHANGE_CKF_FORCE_ALL_NOW
- : TALER_EXCHANGE_CKF_FORCE_DOWNLOAD);
- /* Redownload /keys. */
- GNUNET_break (GNUNET_TIME_absolute_is_zero (rdate.abs_time));
- return;
- }
- {
- const struct TALER_EXCHANGE_Keys *keys;
-
- keys = TALER_EXCHANGE_get_keys (is->exchange);
- if (NULL == keys)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- cks->my_denom_date = keys->last_denom_issue_date;
- }
- 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);
-}
-
-
-/**
- * Offer internal data to a "check_keys" 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 int
-check_keys_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- struct CheckKeysState *cks = cls;
- struct TALER_TESTING_Trait traits[] = {
- /* history entry MUST be first due to response code logic below! */
- TALER_TESTING_make_trait_timestamp (0,
- &cks->my_denom_date),
- TALER_TESTING_trait_end ()
- };
-
- return TALER_TESTING_get_trait (traits,
- ret,
- trait,
- index);
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys (const char *label,
- unsigned int generation)
-{
- struct CheckKeysState *cks;
-
- cks = GNUNET_new (struct CheckKeysState);
- cks->generation = generation;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = cks,
- .label = label,
- .run = &check_keys_run,
- .cleanup = &check_keys_cleanup,
- .traits = &check_keys_traits
- };
-
- return cmd;
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys_pull_all_keys (const char *label,
- unsigned int generation)
-{
- struct TALER_TESTING_Command cmd
- = TALER_TESTING_cmd_check_keys (label,
- generation);
- struct CheckKeysState *cks = cmd.cls;
-
- cks->pull_all_keys = true;
- return cmd;
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_check_keys_with_last_denom (
- const char *label,
- unsigned int generation,
- const char *last_denom_date_ref)
-{
- struct TALER_TESTING_Command cmd
- = TALER_TESTING_cmd_check_keys (label,
- generation);
- struct CheckKeysState *cks = cmd.cls;
- cks->last_denom_date_ref = last_denom_date_ref;
- 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 a0eb35f19..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-2021 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
@@ -69,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.
*/
@@ -98,7 +103,12 @@ struct DepositState
/**
* Deposit handle while operation is running.
*/
- struct TALER_EXCHANGE_DepositHandle *dh;
+ struct TALER_EXCHANGE_BatchDepositHandle *dh;
+
+ /**
+ * Denomination public key of the deposited coin.
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
/**
* Timestamp of the /deposit operation in the wallet (contract signing time).
@@ -131,10 +141,16 @@ 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?
@@ -165,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.
@@ -199,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);
@@ -216,7 +231,7 @@ do_retry (void *cls)
*/
static void
deposit_cb (void *cls,
- const struct TALER_EXCHANGE_DepositResult *dr)
+ const struct TALER_EXCHANGE_BatchDepositResult *dr)
{
struct DepositState *ds = cls;
@@ -240,7 +255,8 @@ deposit_cb (void *cls,
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,
@@ -248,24 +264,20 @@ deposit_cb (void *cls,
return;
}
}
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- dr->hr.http_status,
- ds->is->commands[ds->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (dr->hr.reply,
- 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 == dr->hr.http_status)
{
- ds->deposit_succeeded = GNUNET_YES;
- ds->exchange_timestamp = dr->details.success.deposit_timestamp;
- ds->exchange_pub = *dr->details.success.exchange_pub;
- ds->exchange_sig = *dr->details.success.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);
}
@@ -287,13 +299,12 @@ 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 TALER_MerchantPublicKeyP merchant_pub;
- struct TALER_PrivateContractHash h_contract_terms;
+ struct TALER_PrivateContractHashP h_contract_terms;
enum TALER_ErrorCode ec;
- struct TALER_WireSalt wire_salt;
+ struct TALER_WireSaltP wire_salt;
const char *payto_uri;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("payto_uri",
@@ -302,9 +313,32 @@ deposit_run (void *cls,
&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. */
@@ -327,9 +361,10 @@ deposit_run (void *cls,
ds->contract_terms = json_incref (ods->contract_terms);
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)
{
@@ -383,9 +418,13 @@ 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,
@@ -398,64 +437,79 @@ deposit_run (void *cls,
TALER_TESTING_interpreter_fail (is);
return;
}
- ds->deposit_fee = denom_pub->fee_deposit;
+
+ ds->deposit_fee = ds->denom_pub->fees.deposit;
GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
&coin_pub.eddsa_pub);
- 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);
{
- struct TALER_MerchantWireHash h_wire;
+ struct TALER_MerchantWireHashP h_wire;
GNUNET_assert (GNUNET_OK ==
TALER_JSON_merchant_wire_signature_hash (ds->wire_details,
&h_wire));
TALER_wallet_deposit_sign (&ds->amount,
- &denom_pub->fee_deposit,
+ &ds->denom_pub->fees.deposit,
&h_wire,
&h_contract_terms,
- NULL, /* FIXME: extension hash! */
- &denom_pub->h_key,
+ NULL, /* wallet data hash */
+ phac,
+ NULL, /* hash of extensions */
+ &ds->denom_pub->h_key,
ds->wallet_timestamp,
&merchant_pub,
ds->refund_deadline,
coin_priv,
- &coin_sig);
+ &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,
- ds->wire_deadline,
- payto_uri,
- &wire_salt,
- &h_contract_terms,
- NULL, /* FIXME: extension object */
- &coin_pub,
- denom_pub_sig,
- &denom_pub->key,
- ds->wallet_timestamp,
- &merchant_pub,
- ds->refund_deadline,
- &coin_sig,
- &deposit_cb,
- ds,
- &ec);
if (NULL == ds->dh)
{
GNUNET_break (0);
@@ -483,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)
@@ -520,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);
@@ -537,29 +592,57 @@ 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 */
- TALER_TESTING_make_trait_exchange_pub (0, &ds->exchange_pub),
- TALER_TESTING_make_trait_exchange_sig (0, &ds->exchange_sig),
+ 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_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 (&ds->amount),
- TALER_TESTING_make_trait_deposit_fee_amount (&ds->deposit_fee),
+ 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,
@@ -580,14 +663,15 @@ deposit_traits (void *cls,
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;
@@ -627,7 +711,7 @@ TALER_TESTING_cmd_deposit (const char *label,
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,
@@ -643,15 +727,16 @@ TALER_TESTING_cmd_deposit (const char *label,
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;
@@ -673,21 +758,25 @@ TALER_TESTING_cmd_deposit_with_ref (const char *label,
GNUNET_assert (0);
}
ds->wallet_timestamp = GNUNET_TIME_timestamp_get ();
- json_object_set_new (ds->contract_terms,
- "timestamp",
- GNUNET_JSON_from_timestamp (ds->wallet_timestamp));
+ 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_timestamp (refund_deadline);
- json_object_set_new (ds->contract_terms,
- "refund_deadline",
- GNUNET_JSON_from_timestamp (ds->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,
@@ -703,9 +792,10 @@ TALER_TESTING_cmd_deposit_with_ref (const char *label,
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;
diff --git a/src/testing/testing_api_cmd_deposits_get.c b/src/testing/testing_api_cmd_deposits_get.c
index baddb2d00..5d4436e2a 100644
--- a/src/testing/testing_api_cmd_deposits_get.c
+++ b/src/testing/testing_api_cmd_deposits_get.c
@@ -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,10 +55,18 @@ struct TrackTransactionState
unsigned int expected_response_code;
/**
- * Set to the KYC UUID *if* the exchange replied with
+ * 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 kyc_uuid;
+ uint64_t requirement_row;
/**
* Reference to any operation that can provide a transaction.
@@ -99,28 +112,19 @@ deposit_wtid_cb (void *cls,
{
struct TrackTransactionState *tts = cls;
struct TALER_TESTING_Interpreter *is = tts->is;
- struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
tts->tth = NULL;
if (tts->expected_response_code != dr->hr.http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d to command %s in %s:%u\n",
- dr->hr.http_status,
- (int) dr->hr.ec,
- cmd->label,
- __FILE__,
- __LINE__);
- json_dumpf (dr->hr.reply,
- stderr,
- 0);
- TALER_TESTING_interpreter_fail (is);
+ TALER_TESTING_unexpected_status (is,
+ dr->hr.http_status,
+ tts->expected_response_code);
return;
}
switch (dr->hr.http_status)
{
case MHD_HTTP_OK:
- tts->wtid = dr->details.success.wtid;
+ tts->wtid = dr->details.ok.wtid;
if (NULL != tts->bank_transfer_reference)
{
const struct TALER_TESTING_Command *bank_transfer_cmd;
@@ -147,7 +151,7 @@ deposit_wtid_cb (void *cls,
}
/* Compare that expected and gotten subjects match. */
- if (0 != GNUNET_memcmp (&dr->details.success.wtid,
+ if (0 != GNUNET_memcmp (&dr->details.ok.wtid,
wtid_want))
{
GNUNET_break (0);
@@ -158,7 +162,10 @@ deposit_wtid_cb (void *cls,
break;
case MHD_HTTP_ACCEPTED:
/* allowed, nothing to check here */
- tts->kyc_uuid = dr->details.accepted.payment_target_uuid;
+ 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 */
@@ -189,11 +196,11 @@ track_transaction_run (void *cls,
struct TALER_CoinSpendPublicKeyP coin_pub;
const json_t *contract_terms;
const json_t *wire_details;
- struct TALER_MerchantWireHash h_wire_details;
- struct TALER_PrivateContractHash 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,
@@ -265,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);
}
@@ -291,10 +302,8 @@ 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;
}
@@ -321,9 +330,10 @@ track_transaction_traits (void *cls,
struct TrackTransactionState *tts = cls;
struct TALER_TESTING_Trait traits[] = {
TALER_TESTING_make_trait_wtid (&tts->wtid),
- TALER_TESTING_make_trait_payment_target_uuid (&tts->kyc_uuid),
- TALER_TESTING_make_trait_payto_uri (
- (const char **) &tts->merchant_payto_uri),
+ 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 ()
};
diff --git a/src/testing/testing_api_cmd_exec_aggregator.c b/src/testing/testing_api_cmd_exec_aggregator.c
index 0f3cc1e14..1f05576ff 100644
--- a/src/testing/testing_api_cmd_exec_aggregator.c
+++ b/src/testing/testing_api_cmd_exec_aggregator.c
@@ -72,7 +72,6 @@ aggregator_run (void *cls,
"taler-exchange-aggregator",
"taler-exchange-aggregator",
"-c", as->config_filename,
- "-L", "INFO",
"-t", /* exit when done */
(as->kyc_on)
? NULL
diff --git a/src/testing/testing_api_cmd_exec_closer.c b/src/testing/testing_api_cmd_exec_closer.c
index 442fb4ce0..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
@@ -91,6 +91,7 @@ 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,
&reserve_pubp))
@@ -169,7 +170,8 @@ closer_traits (void *cls,
struct TALER_TESTING_Trait xtraits[] = {
TALER_TESTING_make_trait_process (&as->closer_proc),
TALER_TESTING_make_trait_reserve_pub (&as->reserve_pub),
- TALER_TESTING_make_trait_reserve_history (&as->reserve_history),
+ TALER_TESTING_make_trait_reserve_history (0,
+ &as->reserve_history),
TALER_TESTING_trait_end ()
};
@@ -220,11 +222,11 @@ 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 (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_CLOSE;
+ 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_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 f8af443bd..300413b4b 100644
--- a/src/testing/testing_api_cmd_exec_transfer.c
+++ b/src/testing/testing_api_cmd_exec_transfer.c
@@ -67,7 +67,6 @@ transfer_run (void *cls,
"taler-exchange-transfer",
"taler-exchange-transfer",
"-c", as->config_filename,
- "-L", "INFO",
"-S", "1",
"-w", "0",
"-t", /* exit when done */
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 cd31688d5..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,6 +43,11 @@ struct WirewatchState
* Configuration file used by the wirewatcher.
*/
const char *config_filename;
+
+ /**
+ * Account section to be used by the wirewatcher.
+ */
+ const char *account_section;
};
@@ -71,6 +75,10 @@ wirewatch_run (void *cls,
"-S", "1",
"-w", "0",
"-t", /* exit when done */
+ (NULL == ws->account_section)
+ ? NULL
+ : "-a",
+ ws->account_section,
NULL);
if (NULL == ws->wirewatch_proc)
{
@@ -138,14 +146,15 @@ wirewatch_traits (void *cls,
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,
@@ -160,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 d45bd0c67..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.
@@ -71,6 +72,11 @@ struct InsertDepositState
* Deposit fee.
*/
const char *deposit_fee;
+
+ /**
+ * Do we used a cached @e plugin?
+ */
+ bool cached;
};
/**
@@ -79,40 +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)
{
struct GNUNET_TIME_Timestamp now;
memset (issue,
0,
- sizeof (struct TALER_EXCHANGEDB_DenominationKeyInformationP));
+ sizeof (*issue));
now = GNUNET_TIME_timestamp_get ();
- issue->properties.start
- = GNUNET_TIME_timestamp_hton (now);
- issue->properties.expire_withdraw
- = GNUNET_TIME_timestamp_hton (
- GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MINUTES));
- issue->properties.expire_deposit
- = GNUNET_TIME_timestamp_hton (
- GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS));
- issue->properties.expire_legal
- = GNUNET_TIME_timestamp_hton (
- GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_DAYS));
+ 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));
}
@@ -129,32 +132,46 @@ 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 TALER_DenominationPrivateKey denom_priv;
+ char *receiver_wire_account;
(void) cmd;
- // prepare and store issue first.
+ 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);
GNUNET_assert (GNUNET_OK ==
TALER_denom_priv_create (&denom_priv,
&dpk,
- TALER_DENOMINATION_RSA,
+ GNUNET_CRYPTO_BSA_RSA,
1024));
TALER_denom_pub_hash (&dpk,
- &issue.properties.denom_hash);
+ &issue.denom_hash);
if ( (GNUNET_OK !=
- ids->dbc->plugin->start (ids->dbc->plugin->cls,
- "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,
- &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->plugin->commit (ids->plugin->cls)) )
{
TALER_TESTING_interpreter_fail (is);
TALER_denom_pub_free (&dpk);
@@ -166,25 +183,29 @@ 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.hash);
- 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);
@@ -198,67 +219,85 @@ insert_deposit_run (void *cls,
&deposit.coin.coin_pub,
sizeof (deposit.coin.coin_pub));
{
- struct TALER_CoinPubHash c_hash;
+ struct TALER_CoinPubHashP c_hash;
struct TALER_PlanchetDetail pd;
struct TALER_BlindedDenominationSignature bds;
- union TALER_DenominationBlindingKeyP bks;
-
- TALER_blinding_secret_create (&bks);
+ 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, /* FIXME-Oec */
+ NULL, /* no age restriction active */
+ NULL, /* no nonce needed */
&deposit.coin.coin_pub,
+ alg_values,
&c_hash,
- &pd.coin_ev,
- &pd.coin_ev_size));
+ &pd.blinded_planchet));
GNUNET_assert (GNUNET_OK ==
TALER_denom_sign_blinded (&bds,
&denom_priv,
- pd.coin_ev,
- pd.coin_ev_size));
- GNUNET_free (pd.coin_ev);
+ 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_asprintf (&deposit.receiver_wire_account,
- "payto://x-taler-bank/localhost/%s",
+ GNUNET_asprintf (&receiver_wire_account,
+ "payto://x-taler-bank/localhost/%s?receiver-name=%s",
+ ids->merchant_account,
ids->merchant_account);
- memset (&deposit.wire_salt,
+ 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 (deposit.wire_salt));
- deposit.timestamp = GNUNET_TIME_timestamp_get ();
- deposit.wire_deadline = GNUNET_TIME_relative_to_timestamp (
+ sizeof (bd.wire_salt));
+ bd.wallet_timestamp = GNUNET_TIME_timestamp_get ();
+ bd.wire_deadline = GNUNET_TIME_relative_to_timestamp (
ids->wire_deadline);
/* finally, actually perform the DB operation */
{
uint64_t known_coin_id;
- struct TALER_DenominationHash dph;
- struct TALER_AgeHash agh;
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+ bool balance_ok;
+ uint32_t bad_index;
+ bool ctr_conflict;
if ( (GNUNET_OK !=
- ids->dbc->plugin->start (ids->dbc->plugin->cls,
- "libtalertesting: insert deposit")) ||
+ ids->plugin->start (ids->plugin->cls,
+ "libtalertesting: insert deposit")) ||
(0 >
- ids->dbc->plugin->ensure_coin_known (ids->dbc->plugin->cls,
- &deposit.coin,
- &known_coin_id,
- &dph,
- &agh)) ||
+ ids->plugin->ensure_coin_known (ids->plugin->cls,
+ &deposit.coin,
+ &known_coin_id,
+ &dph,
+ &agh)) ||
(GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- ids->dbc->plugin->insert_deposit (ids->dbc->plugin->cls,
- ids->exchange_timestamp,
- &deposit)) ||
+ ids->plugin->do_deposit (ids->plugin->cls,
+ &bd,
+ &ids->exchange_timestamp,
+ &balance_ok,
+ &bad_index,
+ &ctr_conflict)) ||
(GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- ids->dbc->plugin->commit (ids->dbc->plugin->cls)) )
+ ids->plugin->commit (ids->plugin->cls)) )
{
GNUNET_break (0);
- ids->dbc->plugin->rollback (ids->dbc->plugin->cls);
- GNUNET_free (deposit.receiver_wire_account);
+ 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);
@@ -269,7 +308,7 @@ insert_deposit_run (void *cls,
TALER_denom_sig_free (&deposit.coin.denom_sig);
TALER_denom_pub_free (&dpk);
TALER_denom_priv_free (&denom_priv);
- GNUNET_free (deposit.receiver_wire_account);
+ GNUNET_free (receiver_wire_account);
TALER_TESTING_interpreter_next (is);
}
@@ -288,6 +327,14 @@ 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);
}
@@ -295,7 +342,7 @@ insert_deposit_cleanup (void *cls,
struct TALER_TESTING_Command
TALER_TESTING_cmd_insert_deposit (
const char *label,
- const struct TALER_TESTING_DatabaseConnection *dbc,
+ const struct GNUNET_CONFIGURATION_Handle *db_cfg,
const char *merchant_name,
const char *merchant_account,
struct GNUNET_TIME_Timestamp exchange_timestamp,
@@ -303,10 +350,22 @@ TALER_TESTING_cmd_insert_deposit (
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;
diff --git a/src/testing/testing_api_cmd_kyc_check_get.c b/src/testing/testing_api_cmd_kyc_check_get.c
index 3d35752b6..25c7e98b8 100644
--- a/src/testing/testing_api_cmd_kyc_check_get.c
+++ b/src/testing/testing_api_cmd_kyc_check_get.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -73,19 +73,13 @@ check_kyc_cb (void *cls,
{
struct KycCheckGetState *kcg = cls;
struct TALER_TESTING_Interpreter *is = kcg->is;
- struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
kcg->kwh = NULL;
if (kcg->expected_response_code != ks->http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d to command %s in %s:%u\n",
- ks->http_status,
- (int) ks->ec,
- cmd->label,
- __FILE__,
- __LINE__);
- TALER_TESTING_interpreter_fail (is);
+ TALER_TESTING_unexpected_status (is,
+ ks->http_status,
+ kcg->expected_response_code);
return;
}
switch (ks->http_status)
@@ -93,7 +87,7 @@ check_kyc_cb (void *cls,
case MHD_HTTP_OK:
break;
case MHD_HTTP_ACCEPTED:
- kcg->kyc_url = GNUNET_strdup (ks->details.kyc_url);
+ kcg->kyc_url = GNUNET_strdup (ks->details.accepted.kyc_url);
break;
case MHD_HTTP_NO_CONTENT:
break;
@@ -119,15 +113,14 @@ check_kyc_run (void *cls,
{
struct KycCheckGetState *kcg = cls;
const struct TALER_TESTING_Command *res_cmd;
- const char **payto_uri;
- const uint64_t *payment_target;
- struct TALER_PaytoHash h_payto;
+ 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);
+ res_cmd = TALER_TESTING_interpreter_lookup_command (
+ kcg->is,
+ kcg->payment_target_reference);
if (NULL == res_cmd)
{
GNUNET_break (0);
@@ -135,36 +128,37 @@ check_kyc_run (void *cls,
return;
}
if (GNUNET_OK !=
- TALER_TESTING_get_trait_payto_uri (res_cmd,
- &payto_uri))
+ 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_payment_target_uuid (res_cmd,
- &payment_target))
+ TALER_TESTING_get_trait_h_payto (res_cmd,
+ &h_payto))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (kcg->is);
return;
}
- if ( (NULL == *payto_uri) ||
- (0 == *payment_target) )
+ if (0 == *requirement_row)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (kcg->is);
return;
}
- TALER_payto_hash (*payto_uri,
- &h_payto);
- kcg->kwh = TALER_EXCHANGE_kyc_check (is->exchange,
- *payment_target,
- &h_payto,
- GNUNET_TIME_UNIT_SECONDS,
- &check_kyc_cb,
- kcg);
+ 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);
}
@@ -184,10 +178,8 @@ check_kyc_cleanup (void *cls,
if (NULL != kcg->kwh)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %u (%s) did not complete\n",
- kcg->is->ip,
- cmd->label);
+ TALER_TESTING_command_incomplete (kcg->is,
+ cmd->label);
TALER_EXCHANGE_kyc_check_cancel (kcg->kwh);
kcg->kwh = NULL;
}
@@ -213,8 +205,7 @@ check_kyc_traits (void *cls,
{
struct KycCheckGetState *kcg = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_kyc_url (
- (const char **) &kcg->kyc_url),
+ TALER_TESTING_make_trait_kyc_url (kcg->kyc_url),
TALER_TESTING_trait_end ()
};
diff --git a/src/testing/testing_api_cmd_kyc_proof.c b/src/testing/testing_api_cmd_kyc_proof.c
index fdd3affdc..b079fffce 100644
--- a/src/testing/testing_api_cmd_kyc_proof.c
+++ b/src/testing/testing_api_cmd_kyc_proof.c
@@ -44,9 +44,9 @@ struct KycProofGetState
const char *code;
/**
- * State to pass.
+ * Logic section name to pass to `/kyc-proof/` handler.
*/
- const char *state;
+ const char *logic;
/**
* Expected HTTP response code.
@@ -83,18 +83,13 @@ proof_kyc_cb (void *cls,
{
struct KycProofGetState *kcg = cls;
struct TALER_TESTING_Interpreter *is = kcg->is;
- struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
kcg->kph = NULL;
if (kcg->expected_response_code != kpr->http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- kpr->http_status,
- cmd->label,
- __FILE__,
- __LINE__);
- TALER_TESTING_interpreter_fail (is);
+ TALER_TESTING_unexpected_status (is,
+ kpr->http_status,
+ kcg->expected_response_code);
return;
}
switch (kpr->http_status)
@@ -131,13 +126,21 @@ proof_kyc_run (void *cls,
{
struct KycProofGetState *kps = cls;
const struct TALER_TESTING_Command *res_cmd;
- const uint64_t *payment_target;
+ const struct TALER_PaytoHashP *h_payto;
+ char *uargs;
+ const char *exchange_url;
(void) cmd;
kps->is = is;
- res_cmd = TALER_TESTING_interpreter_lookup_command (kps->is,
- kps->
- payment_target_reference);
+ 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);
@@ -145,19 +148,28 @@ proof_kyc_run (void *cls,
return;
}
if (GNUNET_OK !=
- TALER_TESTING_get_trait_payment_target_uuid (res_cmd,
- &payment_target))
+ TALER_TESTING_get_trait_h_payto (res_cmd,
+ &h_payto))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (kps->is);
return;
}
- kps->kph = TALER_EXCHANGE_kyc_proof (is->exchange,
- *payment_target,
- kps->code,
- kps->state,
- &proof_kyc_cb,
- kps);
+ 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);
}
@@ -177,10 +189,8 @@ proof_kyc_cleanup (void *cls,
if (NULL != kps->kph)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %u (%s) did not complete\n",
- kps->is->ip,
- cmd->label);
+ TALER_TESTING_command_incomplete (kps->is,
+ cmd->label);
TALER_EXCHANGE_kyc_proof_cancel (kps->kph);
kps->kph = NULL;
}
@@ -206,8 +216,7 @@ proof_kyc_traits (void *cls,
{
struct KycProofGetState *kps = cls;
struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_web_url (
- (const char **) &kps->redirect_url),
+ TALER_TESTING_make_trait_web_url (kps->redirect_url),
TALER_TESTING_trait_end ()
};
@@ -219,17 +228,18 @@ proof_kyc_traits (void *cls,
struct TALER_TESTING_Command
-TALER_TESTING_cmd_proof_kyc (const char *label,
- const char *payment_target_reference,
- const char *code,
- const char *state,
- unsigned int expected_response_code)
+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->state = state;
+ kps->logic = logic_section;
kps->payment_target_reference = payment_target_reference;
kps->expected_response_code = expected_response_code;
{
diff --git a/src/testing/testing_api_cmd_kyc_wallet_get.c b/src/testing/testing_api_cmd_kyc_wallet_get.c
index 91ad09ff9..ffb143ffb 100644
--- a/src/testing/testing_api_cmd_kyc_wallet_get.c
+++ b/src/testing/testing_api_cmd_kyc_wallet_get.c
@@ -49,6 +49,11 @@ struct KycWalletGetState
char *reserve_payto_uri;
/**
+ * Our command.
+ */
+ const struct TALER_TESTING_Command *cmd;
+
+ /**
* Command to get a reserve private key from.
*/
const char *reserve_reference;
@@ -59,10 +64,16 @@ struct KycWalletGetState
unsigned int expected_response_code;
/**
- * Set to the KYC UUID *if* the exchange replied with
- * a request for KYC (#MHD_HTTP_ACCEPTED).
+ * 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 kyc_uuid;
+ uint64_t requirement_row;
/**
* Handle to the "track transaction" pending operation.
@@ -70,6 +81,11 @@ struct KycWalletGetState
struct TALER_EXCHANGE_KycWalletHandle *kwh;
/**
+ * Balance to pass to the exchange.
+ */
+ struct TALER_Amount balance;
+
+ /**
* Interpreter state.
*/
struct TALER_TESTING_Interpreter *is;
@@ -88,33 +104,29 @@ wallet_kyc_cb (void *cls,
{
struct KycWalletGetState *kwg = cls;
struct TALER_TESTING_Interpreter *is = kwg->is;
- struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
kwg->kwh = NULL;
if (kwg->expected_response_code != wkr->http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d (wanted %u) to command %s in %s:%u\n",
- wkr->http_status,
- (int) wkr->ec,
- kwg->expected_response_code,
- cmd->label,
- __FILE__,
- __LINE__);
- TALER_TESTING_interpreter_fail (is);
+ TALER_TESTING_unexpected_status (is,
+ wkr->http_status,
+ kwg->expected_response_code);
return;
}
switch (wkr->http_status)
{
- case MHD_HTTP_OK:
- kwg->kyc_uuid = wkr->payment_target_uuid;
- break;
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;
@@ -136,9 +148,16 @@ wallet_kyc_run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct KycWalletGetState *kwg = cls;
+ const char *exchange_url;
- (void) cmd;
+ 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;
@@ -170,12 +189,15 @@ wallet_kyc_run (void *cls,
GNUNET_CRYPTO_eddsa_key_get_public (&kwg->reserve_priv.eddsa_priv,
&kwg->reserve_pub.eddsa_pub);
kwg->reserve_payto_uri
- = TALER_payto_from_reserve (TALER_EXCHANGE_get_base_url (is->exchange),
+ = TALER_reserve_make_payto (exchange_url,
&kwg->reserve_pub);
- kwg->kwh = TALER_EXCHANGE_kyc_wallet (is->exchange,
- &kwg->reserve_priv,
- &wallet_kyc_cb,
- kwg);
+ 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);
}
@@ -195,10 +217,8 @@ wallet_kyc_cleanup (void *cls,
if (NULL != kwg->kwh)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Command %u (%s) did not complete\n",
- kwg->is->ip,
- cmd->label);
+ TALER_TESTING_command_incomplete (kwg->is,
+ cmd->label);
TALER_EXCHANGE_kyc_wallet_cancel (kwg->kwh);
kwg->kwh = NULL;
}
@@ -226,9 +246,9 @@ wallet_kyc_traits (void *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_payment_target_uuid (&kwg->kyc_uuid),
- TALER_TESTING_make_trait_payto_uri (
- (const char **) &kwg->reserve_payto_uri),
+ 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 ()
};
@@ -242,6 +262,7 @@ wallet_kyc_traits (void *cls,
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;
@@ -249,6 +270,9 @@ TALER_TESTING_cmd_wallet_kyc_get (const char *label,
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,
diff --git a/src/testing/testing_api_cmd_oauth.c b/src/testing/testing_api_cmd_oauth.c
index eaf7f9068..80d38e4c8 100644
--- a/src/testing/testing_api_cmd_oauth.c
+++ b/src/testing/testing_api_cmd_oauth.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as
@@ -40,6 +40,11 @@ struct OAuthState
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;
@@ -131,16 +136,17 @@ handle_post (void *cls,
*
* @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)
+ * 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
+ * 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
@@ -153,7 +159,7 @@ handle_post (void *cls,
* 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
+ * 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,
@@ -171,23 +177,36 @@ handler_cb (void *cls,
void **con_cls)
{
struct RequestCtx *rc = *con_cls;
+ struct OAuthState *oas = cls;
unsigned int hc;
json_t *body;
- (void) cls;
(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",
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("id",
- "XXXID12345678"))));
+ "data", data));
return TALER_MHD_reply_json_steal (connection,
body,
MHD_HTTP_OK);
@@ -304,6 +323,7 @@ cleanup (void *cls,
(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);
@@ -327,12 +347,18 @@ oauth_run (void *cls,
struct OAuthState *oas = cls;
(void) cmd;
- oas->mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD,
+ 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);
}
@@ -361,13 +387,15 @@ oauth_cleanup (void *cls,
struct TALER_TESTING_Command
-TALER_TESTING_cmd_oauth (const char *label,
- uint16_t port)
+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,
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_fees.c b/src/testing/testing_api_cmd_offline_sign_wire_fees.c
index 95f52a00b..0fccbcd0a 100644
--- a/src/testing/testing_api_cmd_offline_sign_fees.c
+++ b/src/testing/testing_api_cmd_offline_sign_wire_fees.c
@@ -18,7 +18,7 @@
*/
/**
- * @file testing/testing_api_cmd_offline_sign_fees.c
+ * @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
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 7bd477377..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
@@ -59,75 +59,39 @@ struct RecoupState
struct TALER_ReservePublicKeyP reserve_pub;
/**
+ * Entry in the coin's history generated by this operation.
+ */
+ 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 enum GNUNET_GenericReturnValue
-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 hr HTTP response details
- * @param reserve_pub public key of the reserve receiving the recoup
+ * @param rr response details
*/
static void
recoup_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_ReservePublicKeyP *reserve_pub)
+ 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;
@@ -135,25 +99,17 @@ recoup_cb (void *cls,
ps->ph = NULL;
if (ps->expected_response_code != hr->http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d to command %s in %s:%u\n",
- hr->http_status,
- (int) hr->ec,
- cmd->label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- 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;
@@ -178,12 +134,6 @@ recoup_cb (void *cls,
{
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,
&reserve_priv))
@@ -194,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);
@@ -205,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:
@@ -237,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 union 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;
@@ -264,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,
@@ -274,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_blinding_key (coin_cmd,
- idx,
- &blinding_key))
+ 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_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);
@@ -299,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,
@@ -309,17 +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,
- &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);
}
@@ -368,7 +339,12 @@ recoup_traits (void *cls,
{
struct TALER_TESTING_Trait traits[] = {
TALER_TESTING_make_trait_reserve_pub (&ps->reserve_pub),
- TALER_TESTING_make_trait_reserve_history (&ps->reserve_history),
+ 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 ()
};
diff --git a/src/testing/testing_api_cmd_recoup_refresh.c b/src/testing/testing_api_cmd_recoup_refresh.c
index 9287cdf30..68d267be4 100644
--- a/src/testing/testing_api_cmd_recoup_refresh.c
+++ b/src/testing/testing_api_cmd_recoup_refresh.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2018 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
@@ -44,6 +44,26 @@ struct RecoupRefreshState
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;
@@ -68,91 +88,37 @@ struct RecoupRefreshState
/**
- * 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 enum GNUNET_GenericReturnValue
-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_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 hr HTTP response details
- * @param old_coin_pub public key of the dirty coin
+ * @param rrr response details
*/
static void
recoup_refresh_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_CoinSpendPublicKeyP *old_coin_pub)
+ const struct TALER_EXCHANGE_RecoupRefreshResponse *rrr)
{
- struct RecoupRefreshState *ps = cls;
- struct TALER_TESTING_Interpreter *is = ps->is;
- struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
+ struct RecoupRefreshState *rrs = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &rrr->hr;
+ struct TALER_TESTING_Interpreter *is = rrs->is;
char *cref;
unsigned int idx;
- ps->ph = NULL;
- if (ps->expected_response_code != hr->http_status)
+ rrs->ph = NULL;
+ if (rrs->expected_response_code != hr->http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d to command %s in %s:%u\n",
- hr->http_status,
- (int) hr->ec,
- cmd->label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- stderr,
- 0);
- fprintf (stderr, "\n");
- TALER_TESTING_interpreter_fail (is);
+ TALER_TESTING_unexpected_status (is,
+ hr->http_status,
+ rrs->expected_response_code);
return;
}
if (GNUNET_OK !=
- parse_coin_reference (ps->coin_reference,
- &cref,
- &idx))
+ TALER_TESTING_parse_coin_reference (
+ rrs->coin_reference,
+ &cref,
+ &idx))
{
TALER_TESTING_interpreter_fail (is);
return;
@@ -170,7 +136,7 @@ recoup_refresh_cb (void *cls,
struct TALER_CoinSpendPublicKeyP oc;
melt_cmd = TALER_TESTING_interpreter_lookup_command (is,
- ps->melt_reference);
+ rrs->melt_reference);
if (NULL == melt_cmd)
{
GNUNET_break (0);
@@ -185,7 +151,7 @@ recoup_refresh_cb (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Coin %u not found in command %s\n",
0,
- ps->melt_reference);
+ rrs->melt_reference);
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
@@ -193,7 +159,7 @@ recoup_refresh_cb (void *cls,
GNUNET_CRYPTO_eddsa_key_get_public (&dirty_priv->eddsa_priv,
&oc.eddsa_pub);
if (0 != GNUNET_memcmp (&oc,
- old_coin_pub))
+ &rrr->details.ok.old_coin_pub))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
@@ -228,21 +194,26 @@ recoup_refresh_run (void *cls,
const struct TALER_TESTING_Command *cmd,
struct TALER_TESTING_Interpreter *is)
{
- struct RecoupRefreshState *ps = cls;
+ 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 union TALER_DenominationBlindingKeyP *blinding_key;
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv_old;
const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
const struct TALER_DenominationSignature *coin_sig;
- struct TALER_PlanchetSecretsP planchet;
+ 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;
- ps->is = is;
+ rrs->is = is;
if (GNUNET_OK !=
- parse_coin_reference (ps->coin_reference,
- &cref,
- &idx))
+ TALER_TESTING_parse_coin_reference (
+ rrs->coin_reference,
+ &cref,
+ &idx))
{
TALER_TESTING_interpreter_fail (is);
return;
@@ -251,14 +222,20 @@ recoup_refresh_run (void *cls,
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,
@@ -268,20 +245,49 @@ recoup_refresh_run (void *cls,
TALER_TESTING_interpreter_fail (is);
return;
}
-
if (GNUNET_OK !=
- TALER_TESTING_get_trait_blinding_key (coin_cmd,
- idx,
- &blinding_key))
+ TALER_TESTING_get_trait_coin_priv (melt_cmd,
+ 0,
+ &coin_priv_old))
{
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,
+ &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))
@@ -290,7 +296,6 @@ recoup_refresh_run (void *cls,
TALER_TESTING_interpreter_fail (is);
return;
}
-
if (GNUNET_OK !=
TALER_TESTING_get_trait_denom_sig (coin_cmd,
idx,
@@ -300,18 +305,45 @@ recoup_refresh_run (void *cls,
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));
-
- ps->ph = TALER_EXCHANGE_recoup_refresh (is->exchange,
- denom_pub,
- coin_sig,
- &planchet,
- recoup_refresh_cb,
- ps);
- GNUNET_assert (NULL != ps->ph);
+ 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);
}
@@ -326,13 +358,49 @@ static void
recoup_refresh_cleanup (void *cls,
const struct TALER_TESTING_Command *cmd)
{
- struct RecoupRefreshState *ps = cls;
- if (NULL != ps->ph)
+ struct RecoupRefreshState *rrs = cls;
+ if (NULL != rrs->ph)
{
- TALER_EXCHANGE_recoup_refresh_cancel (ps->ph);
- ps->ph = NULL;
+ TALER_EXCHANGE_recoup_refresh_cancel (rrs->ph);
+ rrs->ph = NULL;
}
- GNUNET_free (ps);
+ 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);
}
@@ -343,15 +411,15 @@ TALER_TESTING_cmd_recoup_refresh (const char *label,
const char *melt_reference,
const char *amount)
{
- struct RecoupRefreshState *ps;
+ struct RecoupRefreshState *rrs;
- ps = GNUNET_new (struct RecoupRefreshState);
- ps->expected_response_code = expected_response_code;
- ps->coin_reference = coin_reference;
- ps->melt_reference = melt_reference;
+ 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,
- &ps->amount))
+ &rrs->amount))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to parse amount `%s' at %s\n",
@@ -361,10 +429,11 @@ TALER_TESTING_cmd_recoup_refresh (const char *label,
}
{
struct TALER_TESTING_Command cmd = {
- .cls = ps,
+ .cls = rrs,
.label = label,
.run = &recoup_refresh_run,
- .cleanup = &recoup_refresh_cleanup
+ .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 d2c2c714c..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-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
@@ -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).
*/
- union 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.
*/
- json_t *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;
@@ -162,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?
@@ -192,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.
@@ -199,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;
@@ -249,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;
@@ -310,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);
@@ -324,23 +373,14 @@ do_reveal_retry (void *cls)
* coming from the exchange, namely the fresh coins.
*
* @param cls closure, a `struct RefreshRevealState`
- * @param hr HTTP response details
- * @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 rr HTTP response details
*/
static void
reveal_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- unsigned int num_coins,
- const struct TALER_PlanchetSecretsP *coin_privs,
- const struct TALER_DenominationSignature *sigs)
+ 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;
@@ -365,24 +405,16 @@ reveal_cb (void *cls,
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",
- hr->http_status,
- (int) hr->ec,
- rrs->is->commands[rrs->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- 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,
@@ -393,16 +425,22 @@ reveal_cb (void *cls,
TALER_TESTING_interpreter_fail (rrs->is);
return;
}
- rrs->num_fresh_coins = num_coins;
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,
@@ -412,18 +450,25 @@ 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;
- TALER_denom_sig_deep_copy (&fc->sig,
- &sigs[i]);
+ 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:
@@ -444,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)
@@ -452,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);
@@ -461,13 +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,
- 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);
@@ -493,10 +563,8 @@ refresh_reveal_cleanup (void *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;
}
@@ -507,10 +575,14 @@ refresh_reveal_cleanup (void *cls,
}
for (unsigned int j = 0; j < rrs->num_fresh_coins; j++)
+ {
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 (rrs->fresh_coins);
- rrs->fresh_coins = NULL;
+ GNUNET_free (rrs->psa);
rrs->num_fresh_coins = 0;
GNUNET_free (rrs);
}
@@ -540,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);
@@ -554,28 +625,15 @@ do_link_retry (void *cls)
* withdrawn by the "refresh reveal" CMD.
*
* @param cls closure.
- * @param hr HTTP response details
- * @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 lr HTTP response details
*/
static void
link_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- unsigned int num_coins,
- const struct TALER_CoinSpendPrivateKeyP *coin_privs,
- const struct TALER_DenominationSignature *sigs,
- const struct TALER_DenominationPublicKey *pubs)
+ 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;
@@ -601,24 +659,16 @@ link_cb (void *cls,
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",
- hr->http_status,
- (int) hr->ec,
- link_cmd->label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- 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,
@@ -642,11 +692,11 @@ link_cb (void *cls,
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__);
@@ -654,11 +704,11 @@ 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++)
+ 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 (&coin_privs[i],
- &coin_privs[j]))
+ 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;
@@ -676,29 +726,38 @@ link_cb (void *cls,
return;
}
- for (unsigned int i = 0; i<num_coins; i++)
- for (unsigned int j = 0; j<num_coins; j++)
+ 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 (&coin_privs[i],
- &(*fc)[j].coin_priv)) &&
+ GNUNET_memcmp (&fcj->coin_priv,
+ &lci_i->coin_priv)) &&
(0 ==
- TALER_denom_sig_cmp (&(*fc)[i].sig,
- &sigs[j])) &&
+ TALER_denom_sig_cmp (&fcj->sig,
+ &lci_i->sig)) &&
(0 ==
- TALER_denom_pub_cmp (&(*fc)[i].pk->key,
- &pubs[j])) )
+ TALER_denom_pub_cmp (&fcj->pk->key,
+ &lci_i->pub)) )
{
found++;
break;
}
- }
+ } /* for j*/
+ } /* for i */
}
- if (found != num_coins)
+ 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;
@@ -707,9 +766,9 @@ 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:
@@ -740,9 +799,16 @@ 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;
- (void) cmd;
+ rls->cmd = cmd;
rls->is = is;
+ 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)
@@ -762,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);
@@ -784,10 +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)
{
@@ -813,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;
}
@@ -831,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`
@@ -854,13 +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 ();
- if (NULL != rms->refresh_data)
- {
- json_decref (rms->refresh_data);
- rms->refresh_data = NULL;
- }
+ TALER_TESTING_touch_cmd (rms->is);
melt_run (rms,
NULL,
rms->is);
@@ -873,20 +921,15 @@ do_melt_retry (void *cls)
* CMD was set to do so.
*
* @param cls closure.
- * @param hr HTTP response details
- * @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 mr melt response details
*/
static void
melt_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- uint32_t noreveal_index,
- const struct TALER_ExchangePublicKeyP *exchange_pub)
+ const struct TALER_EXCHANGE_MeltResponse *mr)
{
struct RefreshMeltState *rms = cls;
+ const struct TALER_EXCHANGE_HttpResponse *hr = &mr->hr;
- (void) exchange_pub;
rms->rmh = NULL;
if (rms->expected_response_code != hr->http_status)
{
@@ -909,44 +952,57 @@ melt_cb (void *cls,
MAX_BACKOFF);
rms->total_backoff = GNUNET_TIME_relative_add (rms->total_backoff,
rms->backoff);
- rms->is->commands[rms->is->ip].num_tries++;
+ 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",
- hr->http_status,
- (int) hr->ec,
- rms->is->commands[rms->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- 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,
- &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,29 +1029,34 @@ melt_run (void *cls,
};
const char **melt_fresh_amounts;
- (void) cmd;
+ 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);
@@ -1011,8 +1072,26 @@ melt_run (void *cls,
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);
+ return;
+ }
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))
@@ -1030,9 +1109,14 @@ melt_run (void *cls,
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;
@@ -1044,12 +1128,14 @@ melt_run (void *cls,
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);
@@ -1064,30 +1150,49 @@ melt_run (void *cls,
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 */
- TALER_denom_pub_deep_copy (&rms->fresh_pks[i].key,
- &fresh_pk->key);
+ TALER_denom_pub_copy (&rms->fresh_pks[i].key,
+ &fresh_pk->key);
} /* end for */
- GNUNET_assert (NULL == rms->refresh_data);
- rms->refresh_data
- = TALER_EXCHANGE_refresh_prepare (rms->melt_priv,
- &melt_amount,
- melt_sig,
- melt_denom_pub,
- num_fresh_coins,
- rms->fresh_pks);
- if (NULL == rms->refresh_data)
+
+ 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,
- &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)
{
@@ -1115,9 +1220,8 @@ melt_cleanup (void *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;
}
@@ -1130,11 +1234,14 @@ melt_cleanup (void *cls,
{
for (unsigned int i = 0; i < rms->num_fresh_coins; i++)
TALER_denom_pub_free (&rms->fresh_pks[i].key);
+ GNUNET_free (rms->fresh_pks);
+ }
+ 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->fresh_pks);
- rms->fresh_pks = NULL;
- json_decref (rms->refresh_data);
- rms->refresh_data = NULL;
GNUNET_free (rms->melt_fresh_amounts);
GNUNET_free (rms);
}
@@ -1168,6 +1275,21 @@ melt_traits (void *cls,
&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 ()
};
@@ -1241,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);
@@ -1271,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);
@@ -1321,11 +1445,18 @@ refresh_reveal_traits (void *cls,
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),
@@ -1339,6 +1470,8 @@ refresh_reveal_traits (void *cls,
&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 ()
};
diff --git a/src/testing/testing_api_cmd_refund.c b/src/testing/testing_api_cmd_refund.c
index c288e9e15..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-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
@@ -54,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.
@@ -75,38 +80,51 @@ struct RefundState
* response code is acceptable.
*
* @param cls closure
- * @param hr HTTP response details
- * @param exchange_pub public key the exchange
- * used for signing @a obj.
- * @param exchange_sig actual signature confirming the refund
+ * @param rr response details
*/
static void
refund_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const struct TALER_ExchangeSignatureP *exchange_sig)
+ 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 != hr->http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d to command %s in %s:%u\n",
- hr->http_status,
- hr->ec,
- refund_cmd->label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- 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);
}
@@ -125,24 +143,21 @@ refund_run (void *cls,
{
struct RefundState *rs = cls;
const struct TALER_CoinSpendPrivateKeyP *coin_priv;
- struct TALER_CoinSpendPublicKeyP coin;
const json_t *contract_terms;
- struct TALER_PrivateContractHash h_contract_terms;
+ 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;
@@ -168,10 +183,15 @@ refund_run (void *cls,
&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);
@@ -179,7 +199,7 @@ 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,
&merchant_priv))
@@ -188,19 +208,67 @@ refund_run (void *cls,
TALER_TESTING_interpreter_fail (is);
return;
}
- rs->rh = TALER_EXCHANGE_refund (rs->exchange,
- &refund_amount,
- &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.
*
@@ -215,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;
}
@@ -235,7 +301,6 @@ TALER_TESTING_cmd_refund (const char *label,
struct RefundState *rs;
rs = GNUNET_new (struct RefundState);
-
rs->expected_response_code = expected_response_code;
rs->refund_amount = refund_amount;
rs->coin_reference = coin_reference;
@@ -244,7 +309,8 @@ TALER_TESTING_cmd_refund (const char *label,
.cls = rs,
.label = label,
.run = &refund_run,
- .cleanup = &refund_cleanup
+ .cleanup = &refund_cleanup,
+ .traits = &refund_traits
};
return cmd;
@@ -272,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 4522dede1..f734be1a4 100644
--- a/src/testing/testing_api_cmd_revoke.c
+++ b/src/testing/testing_api_cmd_revoke.c
@@ -141,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,
diff --git a/src/testing/testing_api_cmd_revoke_denom_key.c b/src/testing/testing_api_cmd_revoke_denom_key.c
index 7c77c3566..2663c538f 100644
--- a/src/testing/testing_api_cmd_revoke_denom_key.c
+++ b/src/testing/testing_api_cmd_revoke_denom_key.c
@@ -65,28 +65,22 @@ struct RevokeState
* Function called with information about the post revocation operation result.
*
* @param cls closure with a `struct RevokeState *`
- * @param hr HTTP response data
+ * @param rdr response data
*/
static void
success_cb (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+ 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)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- hr->http_status,
- rs->is->commands[rs->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- stderr,
- 0);
- TALER_TESTING_interpreter_fail (rs->is);
+ TALER_TESTING_unexpected_status (rs->is,
+ hr->http_status,
+ rs->expected_response_code);
return;
}
TALER_TESTING_interpreter_next (rs->is);
@@ -158,7 +152,23 @@ revoke_run (void *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,
@@ -185,13 +195,27 @@ revoke_run (void *cls,
}
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,
- &is->master_priv,
+ master_priv,
&master_sig);
}
rs->kh = TALER_EXCHANGE_management_revoke_denomination_key (
- is->ctx,
- is->exchange_url,
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
&denom_pub->h_key,
&master_sig,
&success_cb,
diff --git a/src/testing/testing_api_cmd_revoke_sign_key.c b/src/testing/testing_api_cmd_revoke_sign_key.c
index 9745d728d..65b80b4c9 100644
--- a/src/testing/testing_api_cmd_revoke_sign_key.c
+++ b/src/testing/testing_api_cmd_revoke_sign_key.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
@@ -65,28 +65,22 @@ struct RevokeState
* Function called with information about the post revocation operation result.
*
* @param cls closure with a `struct RevokeState *`
- * @param hr HTTP response data
+ * @param rsr response data
*/
static void
success_cb (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+ 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)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- hr->http_status,
- rs->is->commands[rs->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- stderr,
- 0);
- TALER_TESTING_interpreter_fail (rs->is);
+ TALER_TESTING_unexpected_status (rs->is,
+ hr->http_status,
+ rs->expected_response_code);
return;
}
TALER_TESTING_interpreter_next (rs->is);
@@ -158,7 +152,23 @@ revoke_run (void *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,
@@ -185,13 +195,27 @@ revoke_run (void *cls,
}
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,
- &is->master_priv,
+ master_priv,
&master_sig);
}
rs->kh = TALER_EXCHANGE_management_revoke_signing_key (
- is->ctx,
- is->exchange_url,
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
exchange_pub,
&master_sig,
&success_cb,
diff --git a/src/testing/testing_api_cmd_rewind.c b/src/testing/testing_api_cmd_rewind.c
deleted file mode 100644
index 93b38d3c3..000000000
--- a/src/testing/testing_api_cmd_rewind.c
+++ /dev/null
@@ -1,197 +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_rewind.c
- * @brief command to rewind the instruction pointer.
- * @author Marcello Stanisci
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_exchange_service.h"
-#include "taler_testing_lib.h"
-
-
-/**
- * State for a "rewind" CMD.
- */
-struct RewindIpState
-{
- /**
- * 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;
-};
-
-
-/**
- * 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 (current == target)
- current = NULL;
- if (icmd == target)
- {
- match = icmd;
- break;
- }
- if (TALER_TESTING_cmd_is_batch (icmd))
- {
- int ret = seek_batch (is,
- icmd,
- target);
- if (GNUNET_SYSERR == ret)
- return GNUNET_SYSERR; /* failure! */
- if (GNUNET_OK == ret)
- {
- match = icmd;
- break;
- }
- }
- }
- if (NULL == current)
- {
- /* refuse to jump forward */
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return GNUNET_SYSERR;
- }
- if (NULL == match)
- return GNUNET_NO; /* not found */
- TALER_TESTING_cmd_batch_set_current (cmd,
- new_ip);
- return GNUNET_OK;
-}
-
-
-/**
- * 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;
-
- (void) cmd;
- if (0 == ris->counter)
- {
- 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];
-
- 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;
- }
-}
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 d3b34a934..000000000
--- a/src/testing/testing_api_cmd_serialize_keys.c
+++ /dev/null
@@ -1,293 +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 exchange.
- */
- 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 (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 enum GNUNET_GenericReturnValue
-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 (sks->keys),
- TALER_TESTING_make_trait_exchange_url (
- (const char **) &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,
- &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_exchange_url (state_cmd,
- &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
index b458d10c1..460a71e40 100644
--- a/src/testing/testing_api_cmd_set_wire_fee.c
+++ b/src/testing/testing_api_cmd_set_wire_fee.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ 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
@@ -77,27 +77,21 @@ struct WireFeeState
* if the response code is acceptable.
*
* @param cls closure.
- * @param hr HTTP response details
+ * @param sfr response details
*/
static void
wire_add_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+ 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)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- hr->http_status,
- ds->is->commands[ds->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- stderr,
- 0);
- TALER_TESTING_interpreter_fail (ds->is);
+ TALER_TESTING_unexpected_status (ds->is,
+ hr->http_status,
+ ds->expected_response_code);
return;
}
TALER_TESTING_interpreter_next (ds->is);
@@ -121,10 +115,25 @@ wire_add_run (void *cls,
struct GNUNET_TIME_Absolute now;
struct GNUNET_TIME_Timestamp start_time;
struct GNUNET_TIME_Timestamp end_time;
- struct TALER_Amount wire_fee;
- struct TALER_Amount closing_fee;
+ 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 (
@@ -135,10 +144,10 @@ wire_add_run (void *cls,
GNUNET_TIME_UNIT_HOURS));
if ( (GNUNET_OK !=
TALER_string_to_amount (ds->closing_fee,
- &closing_fee)) ||
+ &fees.closing)) ||
(GNUNET_OK !=
TALER_string_to_amount (ds->wire_fee,
- &wire_fee)) )
+ &fees.wire)) )
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
@@ -153,22 +162,34 @@ wire_add_run (void *cls,
}
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,
- &wire_fee,
- &closing_fee,
- &is->master_priv,
+ &fees,
+ master_priv,
&master_sig);
}
ds->dh = TALER_EXCHANGE_management_set_wire_fees (
- is->ctx,
- is->exchange_url,
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
ds->wire_method,
start_time,
end_time,
- &wire_fee,
- &closing_fee,
+ &fees,
&master_sig,
&wire_add_cb,
ds);
@@ -196,10 +217,8 @@ wire_add_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_TESTING_command_incomplete (ds->is,
+ cmd->label);
TALER_EXCHANGE_management_set_wire_fees_cancel (ds->dh);
ds->dh = NULL;
}
diff --git a/src/testing/testing_api_cmd_stat.c b/src/testing/testing_api_cmd_stat.c
index 5d41c05c9..8723aac0d 100644
--- a/src/testing/testing_api_cmd_stat.c
+++ b/src/testing/testing_api_cmd_stat.c
@@ -28,6 +28,19 @@
/**
+ * Run a "stat" CMD.
+ *
+ * @param cls closure.
+ * @param cmd the command being run.
+ * @param is the interpreter state.
+ */
+static void
+stat_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is);
+
+
+/**
* Add the time @a cmd took to the respective duration in @a timings.
*
* @param timings where to add up times
@@ -40,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,
@@ -74,16 +98,18 @@ 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))
{
- struct TALER_TESTING_Command **bcmd;
+ struct TALER_TESTING_Command *bcmd;
if (GNUNET_OK !=
TALER_TESTING_get_trait_batch_cmds (cmd,
@@ -92,18 +118,15 @@ do_stat (struct TALER_TESTING_Timer *timings,
GNUNET_break (0);
return;
}
-
for (unsigned int j = 0;
- NULL != (*bcmd)[j].label;
+ NULL != bcmd[j].label;
j++)
do_stat (timings,
- &(*bcmd)[j]);
- }
- else
- {
- stat_cmd (timings,
- cmd);
+ &bcmd[j]);
+ return;
}
+ stat_cmd (timings,
+ cmd);
}
@@ -121,13 +144,10 @@ 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);
}
@@ -137,7 +157,7 @@ TALER_TESTING_cmd_stat (struct TALER_TESTING_Timer *timers)
{
struct TALER_TESTING_Command cmd = {
.label = "stat",
- .run = stat_run,
+ .run = &stat_run,
.cls = (void *) timers
};
@@ -145,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 f13f60073..000000000
--- a/src/testing/testing_api_cmd_status.c
+++ /dev/null
@@ -1,410 +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)) &&
- (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_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 enum GNUNET_GenericReturnValue
-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))
- {
- 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);
- 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,
- &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,
- &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 hr HTTP response details
- * @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,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- 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 != hr->http_status)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected HTTP response code: %d in %s:%u\n",
- hr->http_status,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- 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<= (unsigned int) 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,
- &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,
- GNUNET_TIME_UNIT_ZERO,
- &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);
-}
-
-
-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 580f6ee85..405c8b7f9 100644
--- a/src/testing/testing_api_cmd_transfer_get.c
+++ b/src/testing/testing_api_cmd_transfer_get.c
@@ -44,6 +44,11 @@ struct TrackTransferState
const char *expected_wire_fee;
/**
+ * Our command.
+ */
+ const struct TALER_TESTING_Command *cmd;
+
+ /**
* Reference to any operation that can provide a WTID.
* Will be the WTID to track.
*/
@@ -98,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;
}
@@ -115,180 +118,178 @@ track_transfer_cleanup (void *cls,
* wire fees and hashed wire details as well.
*
* @param cls closure.
- * @param hr HTTP response details
- * @param ta transfer data returned by the exchange
+ * @param tgr response details
*/
static void
track_transfer_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_EXCHANGE_TransferData *ta)
+ 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;
tts->tth = NULL;
if (tts->expected_response_code != hr->http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d to command %s in %s:%u\n",
- hr->http_status,
- (int) hr->ec,
- cmd->label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- stderr,
- 0);
- TALER_TESTING_interpreter_fail (is);
+ TALER_TESTING_unexpected_status (is,
+ hr->http_status,
+ tts->expected_response_code);
return;
}
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 (&ta->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 (&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_string_to_amount (tts->expected_wire_fee,
- &expected_amount))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
- if (0 != TALER_amount_cmp (&ta->wire_fee,
- &expected_amount))
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Wire fee mismatch to command %s\n",
- cmd->label);
- json_dumpf (hr->reply,
- 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 char **payto_uri;
- struct TALER_PaytoHash h_payto;
-
- wire_details_cmd
- = TALER_TESTING_interpreter_lookup_command (is,
- tts->wire_details_reference);
- if (NULL == wire_details_cmd)
+ 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_TESTING_get_trait_payto_uri (wire_details_cmd,
- &payto_uri))
+ TALER_string_to_amount (tts->expected_total_amount,
+ &expected_amount))
{
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))
+ if (0 != TALER_amount_cmp (&ta->total_amount,
+ &expected_amount))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Wire hash missmath to command %s\n",
- cmd->label);
+ "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 (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))
+ TALER_string_to_amount (tts->expected_wire_fee,
+ &expected_amount))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- if (0 != TALER_amount_cmp (&ta->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);
+ "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);
}
@@ -310,13 +311,13 @@ 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));
wtid_ptr = &wtid;
-
tts->is = is;
if (NULL != tts->wtid_reference)
{
@@ -341,10 +342,13 @@ 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);
}
diff --git a/src/testing/testing_api_cmd_twister_exec_client.c b/src/testing/testing_api_cmd_twister_exec_client.c
index d1d781f5a..bf83c1f80 100644
--- a/src/testing/testing_api_cmd_twister_exec_client.c
+++ b/src/testing/testing_api_cmd_twister_exec_client.c
@@ -572,7 +572,7 @@ 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,
diff --git a/src/testing/testing_api_cmd_wire.c b/src/testing/testing_api_cmd_wire.c
index 66d018c79..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,19 +77,14 @@ struct WireState
* that the wire fee is acceptable too.
*
* @param cls closure.
- * @param hr HTTP response details
- * @param accounts_len length of the @a accounts array.
- * @param accounts list of wire accounts of the exchange,
- * NULL on error.
+ * @param wr response details
*/
static void
wire_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- unsigned int accounts_len,
- const struct TALER_EXCHANGE_WireAccount *accounts)
+ 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;
TALER_LOG_DEBUG ("Checking parsed /wire response\n");
@@ -100,41 +100,70 @@ wire_cb (void *cls,
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));
- for (const struct TALER_EXCHANGE_WireAggregateFees *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)
{
@@ -163,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);
}
@@ -186,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;
}
@@ -197,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
index c07e9bba6..d2a15894a 100644
--- a/src/testing/testing_api_cmd_wire_add.c
+++ b/src/testing/testing_api_cmd_wire_add.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
@@ -67,27 +67,21 @@ struct WireAddState
* if the response code is acceptable.
*
* @param cls closure.
- * @param hr HTTP response details
+ * @param wer response details
*/
static void
wire_add_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+ 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)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- hr->http_status,
- ds->is->commands[ds->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- stderr,
- 0);
- TALER_TESTING_interpreter_fail (ds->is);
+ TALER_TESTING_unexpected_status (ds->is,
+ hr->http_status,
+ ds->expected_response_code);
return;
}
TALER_TESTING_interpreter_next (ds->is);
@@ -110,10 +104,30 @@ wire_add_run (void *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,
@@ -125,23 +139,50 @@ wire_add_run (void *cls,
}
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,
- &is->master_priv,
+ master_priv,
&master_sig1);
TALER_exchange_wire_signature_make (ds->payto_uri,
- &is->master_priv,
+ NULL,
+ debit_rest,
+ credit_rest,
+ master_priv,
&master_sig2);
}
ds->dh = TALER_EXCHANGE_management_enable_wire (
- is->ctx,
- is->exchange_url,
+ 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);
@@ -166,10 +207,8 @@ wire_add_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_TESTING_command_incomplete (ds->is,
+ cmd->label);
TALER_EXCHANGE_management_enable_wire_cancel (ds->dh);
ds->dh = NULL;
}
diff --git a/src/testing/testing_api_cmd_wire_del.c b/src/testing/testing_api_cmd_wire_del.c
index 15d29d727..50ebfc7cb 100644
--- a/src/testing/testing_api_cmd_wire_del.c
+++ b/src/testing/testing_api_cmd_wire_del.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ Copyright (C) 2020, 2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
@@ -67,27 +67,22 @@ struct WireDelState
* if the response code is acceptable.
*
* @param cls closure.
- * @param hr HTTP response details
+ * @param wdr response details
*/
static void
wire_del_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr)
+ 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)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u to command %s in %s:%u\n",
- hr->http_status,
- ds->is->commands[ds->is->ip].label,
- __FILE__,
- __LINE__);
- json_dumpf (hr->reply,
- stderr,
- 0);
- TALER_TESTING_interpreter_fail (ds->is);
+ TALER_TESTING_unexpected_status (ds->is,
+ hr->http_status,
+ ds->expected_response_code);
+
return;
}
TALER_TESTING_interpreter_next (ds->is);
@@ -109,8 +104,24 @@ wire_del_run (void *cls,
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)
@@ -121,14 +132,28 @@ wire_del_run (void *cls,
}
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,
- &is->master_priv,
+ master_priv,
&master_sig);
}
ds->dh = TALER_EXCHANGE_management_disable_wire (
- is->ctx,
- is->exchange_url,
+ TALER_TESTING_interpreter_get_context (is),
+ exchange_url,
ds->payto_uri,
now,
&master_sig,
@@ -158,10 +183,8 @@ wire_del_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_TESTING_command_incomplete (ds->is,
+ cmd->label);
TALER_EXCHANGE_management_disable_wire_cancel (ds->dh);
ds->dh = NULL;
}
diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c
index 8e6cba704..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-2021 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"
@@ -61,11 +62,16 @@ struct WithdrawState
/**
* Reference to a withdraw or reveal operation from which we should
- * re-use the private coin key, or NULL for regular withdrawal.
+ * 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.
@@ -100,6 +106,22 @@ struct WithdrawState
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;
@@ -113,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.
@@ -142,10 +176,16 @@ struct WithdrawState
struct GNUNET_TIME_Relative total_backoff;
/**
- * Set to the KYC UUID *if* the exchange replied with
+ * 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 kyc_uuid;
+ uint64_t requirement_row;
/**
* Expected HTTP response code to the request.
@@ -185,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);
@@ -203,7 +242,7 @@ do_retry (void *cls)
*/
static void
reserve_withdraw_cb (void *cls,
- const struct TALER_EXCHANGE_WithdrawResponse *wr)
+ const struct TALER_EXCHANGE_BatchWithdrawResponse *wr)
{
struct WithdrawState *ws = cls;
struct TALER_TESTING_Interpreter *is = ws->is;
@@ -213,12 +252,12 @@ reserve_withdraw_cb (void *cls,
{
if (0 != ws->do_retry)
{
- if (TALER_EC_EXCHANGE_WITHDRAW_RESERVE_UNKNOWN != wr->hr.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 == 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_WITHDRAW_RESERVE_UNKNOWN == 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,
@@ -228,7 +267,7 @@ reserve_withdraw_cb (void *cls,
/* on DB conflicts, do not use backoff */
if (TALER_EC_GENERIC_DB_SOFT_FAILURE == wr->hr.ec)
ws->backoff = GNUNET_TIME_UNIT_ZERO;
- else if (TALER_EC_EXCHANGE_WITHDRAW_RESERVE_UNKNOWN != wr->hr.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,
@@ -237,45 +276,38 @@ 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",
- wr->hr.http_status,
- (int) wr->hr.ec,
- TALER_TESTING_interpreter_get_current_label (is),
- __FILE__,
- __LINE__);
- json_dumpf (wr->hr.reply,
- 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 (wr->hr.http_status)
{
case MHD_HTTP_OK:
- TALER_denom_sig_deep_copy (&ws->sig,
- &wr->details.success.sig);
+ 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_ACCEPTED:
- /* nothing to check */
- ws->kyc_uuid = wr->details.accepted.payment_target_uuid;
- break;
case MHD_HTTP_FORBIDDEN:
/* nothing to check */
break;
@@ -288,6 +320,13 @@ reserve_withdraw_cb (void *cls,
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,
@@ -301,50 +340,6 @@ reserve_withdraw_cb (void *cls,
/**
- * 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 enum GNUNET_GenericReturnValue
-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;
-}
-
-
-/**
* Run the command.
*/
static void
@@ -357,7 +352,8 @@ 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 (
@@ -379,43 +375,45 @@ withdraw_run (void *cls,
}
if (NULL == ws->exchange_url)
ws->exchange_url
- = GNUNET_strdup (TALER_EXCHANGE_get_base_url (is->exchange));
+ = 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_payto_from_reserve (ws->exchange_url,
+ = TALER_reserve_make_payto (ws->exchange_url,
&ws->reserve_pub);
+
if (NULL == ws->reuse_coin_key_ref)
{
- TALER_planchet_setup_random (&ws->ps);
+ TALER_planchet_master_setup_random (&ws->ps);
}
else
{
- const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+ const struct TALER_PlanchetMasterSecretP *ps;
const struct TALER_TESTING_Command *cref;
char *cstr;
unsigned int index;
GNUNET_assert (GNUNET_OK ==
- parse_coin_reference (ws->reuse_coin_key_ref,
- &cstr,
- &index));
+ 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_coin_priv (cref,
- index,
- &coin_priv));
- TALER_planchet_setup_random (&ws->ps);
- ws->ps.coin_priv = *coin_priv;
+ 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,
@@ -433,18 +431,30 @@ withdraw_run (void *cls,
{
ws->amount = ws->pk->value;
}
+
ws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL;
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);
@@ -469,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)
@@ -481,11 +490,14 @@ withdraw_cleanup (void *cls,
ws->retry_task = 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;
}
+ 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);
@@ -511,11 +523,15 @@ withdraw_traits (void *cls,
struct WithdrawState *ws = cls;
struct TALER_TESTING_Trait traits[] = {
/* history entry MUST be first due to response code logic below! */
- TALER_TESTING_make_trait_reserve_history (&ws->reserve_history),
+ TALER_TESTING_make_trait_reserve_history (0,
+ &ws->reserve_history),
TALER_TESTING_make_trait_coin_priv (0 /* only one coin */,
- &ws->ps.coin_priv),
+ &ws->coin_priv),
+ TALER_TESTING_make_trait_planchet_secret (&ws->ps),
TALER_TESTING_make_trait_blinding_key (0 /* only one coin */,
- &ws->ps.blinding_key),
+ &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 */,
@@ -523,11 +539,18 @@ withdraw_traits (void *cls,
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_payment_target_uuid (&ws->kyc_uuid),
- TALER_TESTING_make_trait_payto_uri (
- (const char **) &ws->reserve_payto_uri),
- TALER_TESTING_make_trait_exchange_url (
- (const char **) &ws->exchange_url),
+ 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 ()
};
@@ -540,26 +563,34 @@ withdraw_traits (void *cls,
}
-/**
- * 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,
@@ -586,26 +617,12 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
}
-/**
- * Create a withdraw command, letting the caller specify
- * 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 coin_ref reference to (withdraw/reveal) command of a coin
- * from which we should re-use 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_reuse_key (
const char *label,
const char *reserve_reference,
const char *amount,
+ uint8_t age,
const char *coin_ref,
unsigned int expected_response_code)
{
@@ -614,6 +631,7 @@ TALER_TESTING_cmd_withdraw_amount_reuse_key (
cmd = TALER_TESTING_cmd_withdraw_amount (label,
reserve_reference,
amount,
+ age,
expected_response_code);
{
struct WithdrawState *ws = cmd.cls;
@@ -624,18 +642,6 @@ TALER_TESTING_cmd_withdraw_amount_reuse_key (
}
-/**
- * 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_denomination (
const char *label,
@@ -670,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 b74258004..000000000
--- a/src/testing/testing_api_helpers_auditor.c
+++ /dev/null
@@ -1,231 +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 hr http response details
- * @param vi basic information about the auditor
- * @param compat protocol compatibility information
- */
-static void
-auditor_version_cb (void *cls,
- const struct TALER_AUDITOR_HttpResponse *hr,
- const struct TALER_AUDITOR_VersionInformation *vi,
- enum TALER_AUDITOR_VersionCompatibility compat)
-{
- struct TALER_TESTING_Interpreter *is = cls;
-
- (void) hr;
- 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 d3c7a2d73..000000000
--- a/src/testing/testing_api_helpers_bank.c
+++ /dev/null
@@ -1,687 +0,0 @@
-/*
- This file is part of TALER
- 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 Foundation; either version 3, or
- (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received 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)
-
-
-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;
-}
-
-
-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));
-}
-
-
-struct TALER_TESTING_LibeufinServices
-TALER_TESTING_run_libeufin (const struct TALER_TESTING_BankConfiguration *bc)
-{
- struct GNUNET_OS_Process *nexus_proc;
- struct GNUNET_OS_Process *sandbox_proc;
- struct TALER_TESTING_LibeufinServices ret = { 0 };
- unsigned int iter;
- char *curl_check_cmd;
-
- nexus_proc = GNUNET_OS_start_process (
- GNUNET_OS_INHERIT_STD_ERR,
- NULL, NULL, NULL,
- "libeufin-nexus",
- "libeufin-nexus",
- "serve",
- "--db-name", "/tmp/nexus-exchange-test.sqlite3",
- NULL);
- if (NULL == nexus_proc)
- {
- GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
- "exec",
- "libeufin-nexus");
- return ret;
- }
- GNUNET_asprintf (&curl_check_cmd,
- "curl -s %s",
- bc->exchange_auth.wire_gateway_url);
- /* give child time to start and bind against the socket */
- fprintf (stderr,
- "Waiting for `nexus' to be ready (via %s)\n", curl_check_cmd);
- iter = 0;
- do
- {
- if (10 == iter)
- {
- fprintf (
- stderr,
- "Failed to launch `nexus'\n");
- GNUNET_OS_process_kill (nexus_proc,
- SIGTERM);
- GNUNET_OS_process_wait (nexus_proc);
- GNUNET_OS_process_destroy (nexus_proc);
- GNUNET_free (curl_check_cmd);
- GNUNET_break (0);
- return ret;
- }
- fprintf (stderr, ".");
- sleep (1);
- iter++;
- }
- while (0 != system (curl_check_cmd));
-
- // start sandbox.
- GNUNET_free (curl_check_cmd);
- fprintf (stderr, "\n");
-
- sandbox_proc = GNUNET_OS_start_process (
- GNUNET_OS_INHERIT_STD_ERR,
- NULL, NULL, NULL,
- "libeufin-sandbox",
- "libeufin-sandbox",
- "serve",
- "--db-name", "/tmp/sandbox-exchange-test.sqlite3",
- NULL);
- if (NULL == sandbox_proc)
- {
- GNUNET_break (0);
- return ret;
- }
-
- /* give child time to start and bind against the socket */
- fprintf (stderr,
- "Waiting for `sandbox' to be ready.\n");
- iter = 0;
- do
- {
- if (10 == iter)
- {
- fprintf (
- stderr,
- "Failed to launch `sandbox'\n");
- GNUNET_OS_process_kill (sandbox_proc,
- SIGTERM);
- GNUNET_OS_process_wait (sandbox_proc);
- GNUNET_OS_process_destroy (sandbox_proc);
- GNUNET_break (0);
- return ret;
- }
- fprintf (stderr, ".");
- sleep (1);
- iter++;
- }
- while (0 != system ("curl -s http://localhost:5000/"));
- fprintf (stderr, "\n");
-
- // Creates nexus user + bank loopback connection + Taler facade.
- if (0 != system ("taler-nexus-prepare"))
- {
- GNUNET_OS_process_kill (nexus_proc, SIGTERM);
- GNUNET_OS_process_wait (nexus_proc);
- GNUNET_OS_process_destroy (nexus_proc);
- GNUNET_OS_process_kill (sandbox_proc, SIGTERM);
- GNUNET_OS_process_wait (sandbox_proc);
- GNUNET_OS_process_destroy (sandbox_proc);
- TALER_LOG_ERROR ("Could not prepare nexus\n");
- GNUNET_break (0);
- return ret;
- }
- ret.nexus = nexus_proc;
- ret.sandbox = sandbox_proc;
- return ret;
-}
-
-
-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;
- 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);
- }
- GNUNET_CONFIGURATION_destroy (cfg);
- bank_proc = GNUNET_OS_start_process (
- GNUNET_OS_INHERIT_STD_ERR,
- NULL, NULL, NULL,
- "taler-bank-manage-testing",
- "taler-bank-manage-testing",
- config_filename,
- database,
- "serve", 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;
-
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_TESTING_prepare_nexus (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;
- char *database = NULL; // silence compiler
- char *exchange_payto_uri;
-
- GNUNET_assert (0 ==
- strncasecmp (config_section,
- "exchange-account-",
- strlen ("exchange-account-")));
- 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,
- 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 (0 != system (
- "rm -f /tmp/nexus-exchange-test.sqlite3 && rm -f /tmp/sandbox-exchange-test.sqlite3"))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to invoke db-removal command.\n");
- GNUNET_free (database);
- GNUNET_CONFIGURATION_destroy (cfg);
- return GNUNET_SYSERR;
- }
- }
-
- {
- char *csn;
-
- GNUNET_asprintf (&csn,
- "exchange-accountcredentials-%s",
- &config_section[strlen ("exchange-account-")]);
-
-
- if (GNUNET_OK !=
- TALER_BANK_auth_parse_cfg (cfg,
- csn,
- &bc->exchange_auth))
- {
- GNUNET_break (0);
- GNUNET_CONFIGURATION_destroy (cfg);
- GNUNET_free (csn);
- return GNUNET_SYSERR;
- }
- GNUNET_free (csn);
- }
- GNUNET_CONFIGURATION_destroy (cfg);
- bc->exchange_payto = exchange_payto_uri;
- bc->user42_payto =
- "payto://iban/BIC/FR7630006000011234567890189?receiver-name=User42";
- bc->user43_payto =
- "payto://iban/BIC/GB33BUKB20201555555555?receiver-name=User43";
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Relying on nexus %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;
-}
-
-
-enum GNUNET_GenericReturnValue
-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;
-
- GNUNET_assert (0 ==
- strncasecmp (config_section,
- "exchange-account-",
- strlen ("exchange-account-")));
- 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)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Flushing bank database\n");
- if (NULL ==
- (dbreset_proc = GNUNET_OS_start_process (
- GNUNET_OS_INHERIT_STD_ERR,
- 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;
- }
-
- 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);
- GNUNET_free (database);
- return GNUNET_SYSERR;
- }
- if ( (type == GNUNET_OS_PROCESS_EXITED) &&
- (0 != code) )
- {
- fprintf (stderr,
- "Failed to setup database `%s'\n",
- database);
- GNUNET_break (0);
- GNUNET_CONFIGURATION_destroy (cfg);
- GNUNET_free (database);
- return GNUNET_SYSERR;
- }
- GNUNET_free (database);
- 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);
- }
- {
- char *csn;
-
- GNUNET_asprintf (&csn,
- "exchange-accountcredentials-%s",
- &config_section[strlen ("exchange-account-")]);
-
- if (GNUNET_OK !=
- TALER_BANK_auth_parse_cfg (cfg,
- csn,
- &bc->exchange_auth))
- {
- GNUNET_break (0);
- GNUNET_free (csn);
- GNUNET_CONFIGURATION_destroy (cfg);
- return GNUNET_SYSERR;
- }
- GNUNET_free (csn);
- }
- 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,
- "user43_payto: %s\n",
- bc->user43_payto);
- return GNUNET_OK;
-}
-
-
-enum GNUNET_GenericReturnValue
-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, "user43_payto: %s\n",
- bc->user43_payto);
- return GNUNET_OK;
-}
-
-
-json_t *
-TALER_TESTING_make_wire_details (const char *payto)
-{
- struct TALER_WireSalt 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));
-}
-
-
-/* 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 fe7588107..000000000
--- a/src/testing/testing_api_helpers_exchange.c
+++ /dev/null
@@ -1,974 +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"
-
-/**
- * Run multiple taler-exchange-httpd processes in
- * parallel using GNU parallel?
- */
-#define GNU_PARALLEL 0
-
-
-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 @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;
-}
-
-
-enum GNUNET_GenericReturnValue
-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_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;
-}
-
-
-enum GNUNET_GenericReturnValue
-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_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;
-}
-
-
-enum GNUNET_GenericReturnValue
-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_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;
-
- /**
- * 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 enum GNUNET_GenericReturnValue
-sign_keys_for_exchange (void *cls,
- const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- struct SignInfo *si = cls;
- 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 !=
- 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 !=
- 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");
- 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) )
- {
- ret = GNUNET_NO;
- goto fail;
- }
- 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-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
- */
-enum GNUNET_GenericReturnValue
-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,
- .db_reset = reset_db
- };
-
- 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_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)) )
- 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) ) )
- {
- 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_exchange_ready (const char *base_url)
-{
- char *wget_cmd;
- unsigned int iter;
-
- GNUNET_asprintf (&wget_cmd,
- "wget -q -t 1 -T 1 %sseed -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 `taler-exchange-httpd` 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 `taler-exchange-httpd` service (or `wget')\n");
- GNUNET_free (wget_cmd);
- return 77;
- }
- sleep (1);
- iter++;
- }
- while (0 != system (wget_cmd));
- GNUNET_free (wget_cmd);
- return 0;
-}
-
-
-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;
-}
-
-
-/**
- * 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 at %s\n",
- base_url);
- 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;
-}
-
-
-enum GNUNET_GenericReturnValue
-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
- };
- enum GNUNET_GenericReturnValue 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;
-}
-
-
-/**
- * Stop taler-exchange-crypto helpers.
- *
- * @param[in] helpers the process handles.
- */
-static void
-stop_helpers (struct GNUNET_OS_Process *helpers[2])
-{
- for (unsigned int i = 0; i<2; i++)
- {
- if (NULL == helpers[i])
- continue;
- GNUNET_break (0 ==
- GNUNET_OS_process_kill (helpers[i],
- SIGTERM));
- GNUNET_break (GNUNET_OK ==
- GNUNET_OS_process_wait (helpers[i]));
- GNUNET_OS_process_destroy (helpers[i]);
- }
-}
-
-
-/**
- * Start taler-exchange-crypto helpers.
- *
- * @param config_filename configuration file to use
- * @param[out] helpers where to store the process handles.
- */
-static enum GNUNET_GenericReturnValue
-start_helpers (const char *config_filename,
- struct GNUNET_OS_Process *helpers[2])
-{
- char *dir;
- const struct GNUNET_OS_ProjectData *pd;
-
- pd = GNUNET_OS_project_data_get ();
- GNUNET_OS_init (TALER_project_data_default ());
- dir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_BINDIR);
- GNUNET_OS_init (pd);
- if (NULL == dir)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- {
- char *fn;
-
- GNUNET_asprintf (&fn,
- "%s/%s",
- dir,
- "taler-exchange-secmod-eddsa");
- helpers[0] = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- fn,
- "taler-exchange-secmod-eddsa",
- "-c", config_filename,
- "-L", "INFO",
- NULL);
- GNUNET_free (fn);
- }
- {
- char *fn;
-
- GNUNET_asprintf (&fn,
- "%s/%s",
- dir,
- "taler-exchange-secmod-rsa");
- helpers[1] = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
- fn,
- "taler-exchange-secmod-rsa",
- "-c", config_filename,
- "-L", "INFO",
- NULL);
- GNUNET_free (fn);
- }
- GNUNET_free (dir);
- if ( (NULL == helpers[0]) ||
- (NULL == helpers[1]) )
- {
- stop_helpers (helpers);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-enum GNUNET_GenericReturnValue
-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;
- struct GNUNET_OS_Process *helpers[2];
- 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);
- if (GNUNET_OK !=
- start_helpers (setup_ctx->config_filename,
- helpers))
- {
- GNUNET_break (0);
- return 77;
- }
- exchanged = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
- NULL, NULL, NULL,
-#if GNU_PARALLEL
- "parallel",
-#endif
- "taler-exchange-httpd",
- "taler-exchange-httpd",
- "-a", /* some tests may need timetravel */
- "-c", setup_ctx->config_filename,
-#if GNU_PARALLEL
- "-r",
- ":::",
- "-",
- "-",
- "-",
- "-",
-#endif
- NULL);
- if (NULL == exchanged)
- {
- GNUNET_break (0);
- stop_helpers (helpers);
- return 77;
- }
- 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");
- stop_helpers (helpers);
- return GNUNET_NO;
- }
-
- if (0 != TALER_TESTING_wait_exchange_ready (base_url))
- {
- GNUNET_free (base_url);
- stop_helpers (helpers);
- GNUNET_break (0 ==
- GNUNET_OS_process_kill (exchanged,
- SIGTERM));
-#if GNU_PARALLEL
- /* GNU Parallel kills on 2nd SIGTERM, need to give it a
- chance to process the 1st signal first... */
- sleep (1);
- GNUNET_break (0 ==
- GNUNET_OS_process_kill (exchanged,
- SIGTERM));
-#endif
- GNUNET_break (GNUNET_OK ==
- GNUNET_OS_process_wait (exchanged));
- GNUNET_OS_process_destroy (exchanged);
- 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));
-#if GNU_PARALLEL
- /* GNU Parallel kills on 2nd SIGTERM, need to give it a
- chance to process the 1st signal first... */
- sleep (1);
- GNUNET_break (0 ==
- GNUNET_OS_process_kill (exchanged,
- SIGTERM));
-#endif
- GNUNET_break (GNUNET_OK ==
- GNUNET_OS_process_wait (exchanged));
- GNUNET_OS_process_destroy (exchanged);
- stop_helpers (helpers);
- return result;
-}
-
-
-enum GNUNET_GenericReturnValue
-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_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;
-}
-
-
-enum GNUNET_GenericReturnValue
-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);
-}
-
-
-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;
-}
-
-
-/* end of testing_api_helpers_exchange.c */
diff --git a/src/testing/testing_api_loop.c b/src/testing/testing_api_loop.c
index 7a25bed64..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,7 +110,7 @@ TALER_TESTING_interpreter_lookup_command (struct TALER_TESTING_Interpreter *is,
if (TALER_TESTING_cmd_is_batch (cmd))
{
- struct TALER_TESTING_Command **batch;
+ struct TALER_TESTING_Command *batch;
struct TALER_TESTING_Command *current;
struct TALER_TESTING_Command *icmd;
const struct TALER_TESTING_Command *match;
@@ -78,7 +122,7 @@ TALER_TESTING_interpreter_lookup_command (struct TALER_TESTING_Interpreter *is,
/* We must do the loop forward, but we can find the last match */
match = NULL;
for (unsigned int j = 0;
- NULL != (icmd = &(*batch)[j])->label;
+ NULL != (icmd = &batch[j])->label;
j++)
{
if (current == icmd)
@@ -96,53 +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 ();
}
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++;
}
@@ -166,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
{
@@ -180,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++;
@@ -209,19 +254,9 @@ TALER_TESTING_interpreter_fail (struct TALER_TESTING_Interpreter *is)
}
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_end (void)
-{
- static struct TALER_TESTING_Command cmd;
- cmd.label = NULL;
-
- return cmd;
-}
-
-
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];
@@ -229,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)
{
@@ -241,7 +271,6 @@ interpreter_run (void *cls)
struct TALER_TESTING_Command *cmd = &is->commands[is->ip];
is->task = NULL;
-
if (NULL == cmd->label)
{
@@ -259,6 +288,19 @@ interpreter_run (void *cls)
= 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);
@@ -281,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);
@@ -313,20 +347,20 @@ 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 (is->commands);
}
@@ -335,7 +369,7 @@ do_shutdown (void *cls)
/**
* 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)
@@ -353,31 +387,25 @@ 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];
- enum GNUNET_OS_ProcessStatusType type;
- unsigned long code;
+ is->cwh = NULL;
while (TALER_TESTING_cmd_is_batch (cmd))
cmd = TALER_TESTING_cmd_batch_get_current (cmd);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ 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,
&processp))
@@ -386,13 +414,8 @@ maint_child_death (void *cls)
TALER_TESTING_interpreter_fail (is);
return;
}
-
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Got the dead child process handle, waiting for termination ...\n");
- GNUNET_assert (GNUNET_OK ==
- GNUNET_OS_process_wait_status (*processp,
- &type,
- &code));
GNUNET_OS_process_destroy (*processp);
*processp = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -426,7 +449,6 @@ maint_child_death (void *cls)
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);
@@ -436,16 +458,24 @@ maint_child_death (void *cls)
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);
}
@@ -464,28 +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);
}
+#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));
}
@@ -519,347 +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)
{
- static char c;
- int old_errno = errno; /* back-up errno */
+ struct MainContext *main_ctx = cls;
- 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 */
+ 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_cert_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_EXCHANGE_Keys *keys,
- enum TALER_EXCHANGE_VersionCompatibility compat)
+TALER_TESTING_iterate (struct TALER_TESTING_Interpreter *is,
+ bool asc,
+ TALER_TESTING_CommandIterator cb,
+ void *cb_cls)
{
- struct MainContext *main_ctx = cls;
- struct TALER_TESTING_Interpreter *is = main_ctx->is;
+ unsigned int start;
+ unsigned int end;
+ int inc;
- (void) compat;
- if (NULL == keys)
+ if (asc)
{
- if (GNUNET_NO == is->working)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Got NULL response for /keys during startup (%u/%d), retrying!\n",
- hr->http_status,
- (int) hr->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;
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Got NULL response for /keys during execution (%u/%d)!\n",
- hr->http_status,
- (int) hr->ec);
- }
+ inc = 1;
+ start = 0;
+ end = is->ip;
}
else
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Got %d DK from /keys in generation %u\n",
- keys->num_denom_keys,
- is->key_generation + 1);
+ 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];
+
+ cb (cb_cls,
+ cmd);
}
- is->key_generation++;
- is->keys = keys;
+}
- /* /keys has been called for some reason and
- * the interpreter is already running. */
- if (GNUNET_YES == is->working)
- return;
- is->working = GNUNET_YES;
- /* Trigger the next command. */
- TALER_LOG_DEBUG ("Cert_cb, scheduling CMD (ip: %d)\n",
- is->ip);
- GNUNET_SCHEDULER_add_now (&interpreter_run,
- is);
+
+/* ************** 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;
}
/**
- * Initialize scheduler loop and curl context for the testcase,
- * and responsible to run the "run" method.
+ * State for a "rewind" CMD.
+ */
+struct RewindIpState
+{
+ /**
+ * 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;
+};
+
+
+/**
+ * Seek for the @a target command in @a batch (and rewind to it
+ * if successful).
*
- * @param cls closure, typically the "run" method, the
- * interpreter state and a closure for "run".
+ * @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 void
-main_wrapper_exchange_agnostic (void *cls)
+static enum GNUNET_GenericReturnValue
+seek_batch (struct TALER_TESTING_Interpreter *is,
+ const struct TALER_TESTING_Command *cmd,
+ const struct TALER_TESTING_Command *target)
{
- struct MainContext *main_ctx = cls;
+ unsigned int new_ip;
+ struct TALER_TESTING_Command *batch;
+ struct TALER_TESTING_Command *current;
+ struct TALER_TESTING_Command *icmd;
+ struct TALER_TESTING_Command *match;
- main_ctx->main_cb (main_ctx->main_cb_cls,
- main_ctx->is);
+ 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 (current == target)
+ current = NULL;
+ if (icmd == target)
+ {
+ match = icmd;
+ break;
+ }
+ if (TALER_TESTING_cmd_is_batch (icmd))
+ {
+ int ret = seek_batch (is,
+ icmd,
+ target);
+ if (GNUNET_SYSERR == ret)
+ return GNUNET_SYSERR; /* failure! */
+ if (GNUNET_OK == ret)
+ {
+ match = icmd;
+ break;
+ }
+ }
+ }
+ if (NULL == current)
+ {
+ /* refuse to jump forward */
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return GNUNET_SYSERR;
+ }
+ if (NULL == match)
+ return GNUNET_NO; /* not found */
+ TALER_TESTING_cmd_batch_set_current (cmd,
+ new_ip);
+ return GNUNET_OK;
}
/**
- * Function run when the test is aborted before we launch the actual
- * interpreter. Cleans up our state.
+ * Run the "rewind" CMD.
*
- * @param cls the main context
+ * @param cls closure.
+ * @param cmd command being executed now.
+ * @param is the interpreter state.
*/
static void
-do_abort (void *cls)
+rewind_ip_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 RewindIpState *ris = cls;
+ const struct TALER_TESTING_Command *target;
+ unsigned int new_ip;
- is->timeout_task = NULL;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Executing abort prior to interpreter launch\n");
- if (NULL != is->exchange)
+ (void) cmd;
+ if (0 == ris->counter)
{
- TALER_EXCHANGE_disconnect (is->exchange);
- is->exchange = NULL;
+ TALER_TESTING_interpreter_next (is);
+ return;
}
- if (NULL != is->ctx)
+ target
+ = TALER_TESTING_interpreter_lookup_command (is,
+ ris->target_label);
+ if (NULL == target)
{
- GNUNET_CURL_fini (is->ctx);
- is->ctx = NULL;
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
}
- if (NULL != is->rc)
+ ris->counter--;
+ for (new_ip = 0;
+ NULL != is->commands[new_ip].label;
+ new_ip++)
{
- GNUNET_CURL_gnunet_rc_destroy (is->rc);
- is->rc = NULL;
+ const struct TALER_TESTING_Command *cmd = &is->commands[new_ip];
+
+ 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);
}
-/**
- * Initialize scheduler loop and curl context for the testcase,
- * and responsible to run the "run" method.
- *
- * @param cls a `struct MainContext *`
- */
-static void
-main_wrapper_exchange_connect (void *cls)
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_rewind_ip (const char *label,
+ const char *target_label,
+ unsigned int counter)
{
- struct MainContext *main_ctx = cls;
- struct TALER_TESTING_Interpreter *is = main_ctx->is;
- char *exchange_url;
+ struct RewindIpState *ris;
- 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;
+ 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;
}
- main_ctx->exchange_url = exchange_url;
- is->timeout_task = GNUNET_SCHEDULER_add_shutdown (&do_abort,
- main_ctx);
- is->working = GNUNET_YES;
- GNUNET_break
- (NULL != (is->exchange =
- TALER_EXCHANGE_connect (is->ctx,
- exchange_url,
- &TALER_TESTING_cert_cb,
- main_ctx,
- TALER_EXCHANGE_OPTION_END)));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Starting main test loop\n");
- main_ctx->main_cb (main_ctx->main_cb_cls,
- is);
}
/**
- * Load the exchange and auditor key material into @a is.
+ * State for a "authchange" CMD.
+ */
+struct AuthchangeState
+{
+
+ /**
+ * What is the new authorization token to send?
+ */
+ const char *auth_token;
+
+ /**
+ * Old context, clean up on termination.
+ */
+ struct GNUNET_CURL_Context *old_ctx;
+};
+
+
+/**
+ * Run the command.
*
- * @param[in,out] is state to initialize
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
*/
-static int
-load_keys (struct TALER_TESTING_Interpreter *is)
+static void
+authchange_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
{
- char *fn;
+ struct AuthchangeState *ss = cls;
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (is->cfg,
- "exchange-offline",
- "MASTER_PRIV_FILE",
- &fn))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange-offline",
- "MASTER_PRIV_FILE");
- return GNUNET_SYSERR;
- }
- if (GNUNET_SYSERR ==
- GNUNET_DISK_directory_create_for_file (fn))
+ (void) cmd;
+ ss->old_ctx = is->ctx;
+ if (NULL != is->rc)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not setup directory for master private key file `%s'\n",
- fn);
- GNUNET_free (fn);
- return GNUNET_SYSERR;
+ GNUNET_CURL_gnunet_rc_destroy (is->rc);
+ is->rc = NULL;
}
- if (GNUNET_SYSERR ==
- GNUNET_CRYPTO_eddsa_key_from_file (fn,
- GNUNET_YES,
- &is->master_priv.eddsa_priv))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not load master private key from `%s'\n",
- fn);
- GNUNET_free (fn);
- return GNUNET_SYSERR;
+ 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);
}
- GNUNET_free (fn);
- GNUNET_CRYPTO_eddsa_key_get_public (&is->master_priv.eddsa_priv,
- &is->master_pub.eddsa_pub);
+ TALER_TESTING_interpreter_next (is);
+}
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_filename (is->cfg,
- "auditor",
- "AUDITOR_PRIV_FILE",
- &fn))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "auditor",
- "AUDITOR_PRIV_FILE");
- return GNUNET_SYSERR;
- }
- if (GNUNET_SYSERR ==
- GNUNET_DISK_directory_create_for_file (fn))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not setup directory for auditor private key file `%s'\n",
- fn);
- GNUNET_free (fn);
- return GNUNET_SYSERR;
- }
- if (GNUNET_SYSERR ==
- GNUNET_CRYPTO_eddsa_key_from_file (fn,
- GNUNET_YES,
- &is->auditor_priv.eddsa_priv))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not load auditor private key from `%s'\n",
- fn);
- GNUNET_free (fn);
- return GNUNET_SYSERR;
- }
- GNUNET_free (fn);
- GNUNET_CRYPTO_eddsa_key_get_public (&is->auditor_priv.eddsa_priv,
- &is->auditor_pub.eddsa_pub);
- return GNUNET_OK;
+
+/**
+ * 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 GNUNET_CURL_Context *` to clean up.
+ */
+static void
+deferred_cleanup_cb (void *cls)
+{
+ struct GNUNET_CURL_Context *ctx = cls;
+
+ GNUNET_CURL_fini (ctx);
}
/**
- * Load the exchange and auditor URLs from the configuration into @a is.
+ * Cleanup the state from a "authchange" CMD.
*
- * @param[in,out] is state to initialize
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
*/
-static int
-load_urls (struct TALER_TESTING_Interpreter *is)
+static void
+authchange_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
{
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (is->cfg,
- "auditor",
- "BASE_URL",
- &is->auditor_url))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "auditor",
- "BASE_URL");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (is->cfg,
- "exchange",
- "BASE_URL",
- &is->exchange_url))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "BASE_URL");
- GNUNET_free (is->auditor_url);
- return GNUNET_SYSERR;
+ struct AuthchangeState *ss = cls;
+
+ (void) cmd;
+ if (NULL != ss->old_ctx)
+ {
+ (void) GNUNET_SCHEDULER_add_now (&deferred_cleanup_cb,
+ ss->old_ctx);
+ ss->old_ctx = NULL;
}
- return GNUNET_OK;
+ GNUNET_free (ss);
}
-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)
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_authorization (const char *label,
+ const char *auth_token)
{
- 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;
- memset (&is,
- 0,
- sizeof (is));
- is.exchanged = exchanged;
- is.cfg = cfg;
- if (GNUNET_OK !=
- load_keys (&is))
- return GNUNET_SYSERR;
- if (GNUNET_OK !=
- load_urls (&is))
- return GNUNET_SYSERR;
- sigpipe = GNUNET_DISK_pipe (GNUNET_DISK_PF_NONE);
- 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);
+ 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
+ };
- /* 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 (main_ctx.exchange_url);
- GNUNET_SIGNAL_handler_uninstall (shc_chld);
- GNUNET_DISK_pipe_close (sigpipe);
- sigpipe = NULL;
- GNUNET_free (is.auditor_url);
- GNUNET_free (is.exchange_url);
- return is.result;
+ 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_traits.c b/src/testing/testing_api_traits.c
index db94e81a5..799ae6718 100644
--- a/src/testing/testing_api_traits.c
+++ b/src/testing/testing_api_traits.c
@@ -67,7 +67,7 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
return GNUNET_OK;
}
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Trait %s/%u not found.\n",
trait,
index);
@@ -75,4 +75,59 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
}
+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 935d8bcc1..68fbf0082 100644
--- a/src/testing/testing_api_twister_helpers.c
+++ b/src/testing/testing_api_twister_helpers.c
@@ -116,7 +116,9 @@ 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 ();
@@ -129,7 +131,9 @@ TALER_TWISTER_run_twister (const char *config_filename)
&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 ();
@@ -140,7 +144,9 @@ TALER_TWISTER_run_twister (const char *config_filename)
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 ();
@@ -151,7 +157,9 @@ TALER_TWISTER_run_twister (const char *config_filename)
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 f25567f32..d79786ec7 100644
--- a/src/util/.gitignore
+++ b/src/util/.gitignore
@@ -1,8 +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 55ebb4dff..d2504588b 100644
--- a/src/util/Makefile.am
+++ b/src/util/Makefile.am
@@ -10,19 +10,24 @@ endif
pkgcfgdir = $(prefix)/share/taler/config.d/
pkgcfg_DATA = \
+ currencies.conf \
paths.conf \
taler-exchange-secmod-eddsa.conf \
- taler-exchange-secmod-rsa.conf
+ taler-exchange-secmod-rsa.conf \
+ taler-exchange-secmod-cs.conf
EXTRA_DIST = \
$(pkgcfg_DATA) \
taler-config.in \
test_helper_eddsa.conf \
- test_helper_rsa.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-rsa \
+ taler-exchange-secmod-cs
bin_SCRIPTS = \
taler-config
@@ -48,6 +53,16 @@ taler_exchange_secmod_rsa_LDADD = \
$(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
@@ -62,18 +77,22 @@ 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 \
- extensions.c \
- extension_age_restriction.c \
getopt.c \
lang.c \
iban.c \
@@ -91,28 +110,49 @@ libtalerutil_la_SOURCES = \
libtalerutil_la_LIBADD = \
-lgnunetutil \
+ -lgnunetjson \
+ -lsodium \
+ -ljansson \
$(LIBGCRYPT_LIBS) \
-lmicrohttpd $(XLIB) \
+ -lunistring \
+ -lz \
-lm
libtalerutil_la_LDFLAGS = \
- -version-info 0:0:0 \
+ -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
@@ -123,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
@@ -144,6 +185,12 @@ 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 ae9ae652e..cce84d73a 100644
--- a/src/util/amount.c
+++ b/src/util/amount.c
@@ -40,6 +40,31 @@ invalidate (struct TALER_Amount *a)
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)
{
@@ -62,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,
@@ -72,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])
@@ -193,9 +220,8 @@ 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];
}
@@ -205,9 +231,9 @@ TALER_amount_ntoh (struct TALER_Amount *res,
{
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));
}
@@ -219,15 +245,15 @@ TALER_amount_set_zero (const char *cur,
{
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;
}
@@ -236,7 +262,10 @@ enum GNUNET_GenericReturnValue
TALER_amount_is_valid (const struct TALER_Amount *amount)
{
if (amount->value > TALER_AMOUNT_MAX_VALUE)
+ {
+ GNUNET_break (0);
return GNUNET_SYSERR;
+ }
return ('\0' != amount->currency[0]) ? GNUNET_OK : GNUNET_NO;
}
@@ -253,6 +282,20 @@ TALER_amount_is_zero (const struct TALER_Amount *amount)
}
+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;
+}
+
+
/**
* Test if @a a is valid, NBO variant.
*
@@ -539,8 +582,8 @@ 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))
@@ -666,9 +709,9 @@ TALER_amount_multiply (struct TALER_Amount *result,
if (GNUNET_SYSERR ==
TALER_amount_normalize (&in))
return TALER_AAR_INVALID_NORMALIZATION_FAILED;
- memcpy (result->currency,
- amount->currency,
- TALER_CURRENCY_LEN);
+ GNUNET_memcpy (result->currency,
+ amount->currency,
+ TALER_CURRENCY_LEN);
if ( (0 == factor) ||
( (0 == in.value) &&
(0 == in.fraction) ) )
diff --git a/src/util/auditor_signatures.c b/src/util/auditor_signatures.c
index 7b53c21c1..c35b6f192 100644
--- a/src/util/auditor_signatures.c
+++ b/src/util/auditor_signatures.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ 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
@@ -23,20 +23,100 @@
#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_DenominationHash *h_denom_pub,
+ 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_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,
const struct TALER_AuditorPrivateKeyP *auditor_priv,
struct TALER_AuditorSignatureP *auditor_sig)
{
@@ -53,14 +133,8 @@ TALER_auditor_denom_validity_sign (
TALER_amount_hton (&kv.value,
coin_value);
- TALER_amount_hton (&kv.fee_withdraw,
- fee_withdraw);
- TALER_amount_hton (&kv.fee_deposit,
- fee_deposit);
- TALER_amount_hton (&kv.fee_refresh,
- fee_refresh);
- TALER_amount_hton (&kv.fee_refund,
- fee_refund);
+ TALER_denom_fee_set_hton (&kv.fees,
+ fees);
GNUNET_CRYPTO_hash (auditor_url,
strlen (auditor_url) + 1,
&kv.auditor_url_hash);
@@ -73,17 +147,14 @@ TALER_auditor_denom_validity_sign (
enum GNUNET_GenericReturnValue
TALER_auditor_denom_validity_verify (
const char *auditor_url,
- const struct TALER_DenominationHash *h_denom_pub,
+ 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_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,
const struct TALER_AuditorPublicKeyP *auditor_pub,
const struct TALER_AuditorSignatureP *auditor_sig)
{
@@ -100,14 +171,8 @@ TALER_auditor_denom_validity_verify (
TALER_amount_hton (&kv.value,
coin_value);
- TALER_amount_hton (&kv.fee_withdraw,
- fee_withdraw);
- TALER_amount_hton (&kv.fee_deposit,
- fee_deposit);
- TALER_amount_hton (&kv.fee_refresh,
- fee_refresh);
- TALER_amount_hton (&kv.fee_refund,
- fee_refund);
+ TALER_denom_fee_set_hton (&kv.fees,
+ fees);
GNUNET_CRYPTO_hash (auditor_url,
strlen (auditor_url) + 1,
&kv.auditor_url_hash);
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 d368d346e..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,17 +21,8 @@
*/
#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
- */
enum GNUNET_GenericReturnValue
TALER_config_get_amount (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section,
@@ -67,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
- */
+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",
@@ -90,7 +143,8 @@ 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)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Currency `%s' longer than the allowed limit of %u characters.",
@@ -100,5 +154,357 @@ TALER_config_get_currency (const struct GNUNET_CONFIGURATION_Handle *cfg,
*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 178db3aad..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.
@@ -73,9 +73,9 @@ enum GNUNET_GenericReturnValue
TALER_test_coin_valid (const struct TALER_CoinPublicInfo *coin_public_info,
const struct TALER_DenominationPublicKey *denom_pub)
{
- struct TALER_CoinPubHash c_hash;
+ struct TALER_CoinPubHashP c_hash;
#if ENABLE_SANITY_CHECKS
- struct TALER_DenominationHash d_hash;
+ struct TALER_DenominationHashP d_hash;
TALER_denom_pub_hash (denom_pub,
&d_hash);
@@ -83,12 +83,13 @@ TALER_test_coin_valid (const struct TALER_CoinPublicInfo *coin_public_info,
GNUNET_memcmp (&d_hash,
&coin_public_info->denom_pub_hash));
#endif
- // FIXME-Oec: replace with function that
- // also hashes the age vector if we have
- // one!
- GNUNET_CRYPTO_hash (&coin_public_info->coin_pub,
- sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
- &c_hash.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 !=
TALER_denom_pub_verify (denom_pub,
&coin_public_info->denom_sig,
@@ -116,7 +117,6 @@ TALER_link_derive_transfer_secret (
GNUNET_CRYPTO_ecdh_eddsa (&trans_priv->ecdhe_priv,
&coin_pub.eddsa_pub,
&ts->key));
-
}
@@ -147,9 +147,30 @@ TALER_link_recover_transfer_secret (
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);
@@ -167,32 +188,114 @@ TALER_planchet_setup_refresh (const struct TALER_TransferSecretP *secret_seed,
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));
+}
+
+
+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_CoinPubHash *c_hash,
- 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);
if (GNUNET_OK !=
TALER_denom_blind (dk,
- &ps->blinding_key,
- NULL, /* FIXME-Oec */
+ bks,
+ nonce,
+ ach,
&coin_pub,
+ alg_values,
c_hash,
- &pd->coin_ev,
- &pd->coin_ev_size))
+ &pd->blinded_planchet))
{
GNUNET_break (0);
return GNUNET_SYSERR;
@@ -203,20 +306,42 @@ TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,
}
+void
+TALER_planchet_detail_free (struct TALER_PlanchetDetail *pd)
+{
+ TALER_blinded_planchet_free (&pd->blinded_planchet);
+}
+
+
enum GNUNET_GenericReturnValue
TALER_planchet_to_coin (
const struct TALER_DenominationPublicKey *dk,
const struct TALER_BlindedDenominationSignature *blind_sig,
- const struct TALER_PlanchetSecretsP *ps,
- const struct TALER_CoinPubHash *c_hash,
+ 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)
{
- struct TALER_DenominationSignature sig;
-
+ 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 !=
- TALER_denom_sig_unblind (&sig,
+ TALER_denom_sig_unblind (&coin->sig,
blind_sig,
- &ps->blinding_key,
+ bks,
+ c_hash,
+ alg_values,
dk))
{
GNUNET_break_op (0);
@@ -224,15 +349,16 @@ TALER_planchet_to_coin (
}
if (GNUNET_OK !=
TALER_denom_pub_verify (dk,
- &sig,
+ &coin->sig,
c_hash))
{
GNUNET_break_op (0);
- TALER_denom_sig_free (&sig);
+ TALER_denom_sig_free (&coin->sig);
return GNUNET_SYSERR;
}
- coin->sig = sig;
- coin->coin_priv = ps->coin_priv;
+
+ coin->coin_priv = *coin_priv;
+ coin->h_age_commitment = ach;
return GNUNET_OK;
}
@@ -240,6 +366,7 @@ TALER_planchet_to_coin (
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,
@@ -248,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++)
{
@@ -259,7 +390,7 @@ TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc,
hash_context */
for (unsigned int i = 0; i<num_new_coins; i++)
{
- struct TALER_DenominationHash denom_hash;
+ struct TALER_DenominationHashP denom_hash;
/* The denomination keys should / must all be identical regardless
of what offset we use, so we use [0]. */
@@ -294,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);
}
}
@@ -307,22 +437,11 @@ TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc,
void
-TALER_coin_ev_hash (const void *coin_ev,
- size_t coin_ev_size,
- struct TALER_BlindedCoinHash *bch)
-{
- GNUNET_CRYPTO_hash (coin_ev,
- coin_ev_size,
- &bch->hash);
-}
-
-
-void
TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_AgeHash *age_commitment_hash,
- struct TALER_CoinPubHash *coin_h)
+ const struct TALER_AgeCommitmentHash *ach,
+ struct TALER_CoinPubHashP *coin_h)
{
- if (NULL == age_commitment_hash)
+ if (TALER_AgeCommitmentHash_isNullOrZero (ach))
{
/* No age commitment was set */
GNUNET_CRYPTO_hash (&coin_pub->eddsa_pub,
@@ -333,23 +452,93 @@ TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,
{
/* Coin comes with age commitment. Take the hash of the age commitment
* into account */
- const size_t key_s = sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey);
- const size_t age_s = sizeof(struct TALER_AgeHash);
- char data[key_s + age_s];
+ struct GNUNET_HashContext *hash_context;
- GNUNET_memcpy (&data[0],
- &coin_pub->eddsa_pub,
- key_s);
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
- GNUNET_memcpy (&data[key_s],
- age_commitment_hash,
- age_s);
+ GNUNET_CRYPTO_hash_context_read (
+ hash_context,
+ &coin_pub->eddsa_pub,
+ sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey));
- GNUNET_CRYPTO_hash (&data,
- key_s + age_s,
- &coin_h->hash);
+ 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_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
index 5a9ad74e2..e044d31d1 100644
--- a/src/util/crypto_helper_esign.c
+++ b/src/util/crypto_helper_esign.c
@@ -111,21 +111,28 @@ try_connect (struct TALER_CRYPTO_ExchangeSignHelper *esh)
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,
- "taler-exchange-secmod-eddsa",
+ secname,
"UNIXPATH",
&unixpath))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "taler-exchange-secmod-eddsa",
+ secname,
"UNIXPATH");
+ GNUNET_free (secname);
return NULL;
}
/* we use >= here because we want the sun_path to always
@@ -133,12 +140,14 @@ TALER_CRYPTO_helper_esign_connect (
if (strlen (unixpath) >= sizeof (esh->sa.sun_path))
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "taler-exchange-secmod-eddsa",
+ 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;
@@ -357,9 +366,9 @@ TALER_CRYPTO_helper_esign_sign_ (
sr->header.size = htons (sizeof (buf));
sr->header.type = htons (TALER_HELPER_EDDSA_MT_REQ_SIGN);
sr->reserved = htonl (0);
- memcpy (&sr->purpose,
- purpose,
- purpose_size);
+ GNUNET_memcpy (&sr->purpose,
+ purpose,
+ purpose_size);
if (GNUNET_OK !=
TALER_crypto_helper_send_all (esh->sock,
buf,
diff --git a/src/util/crypto_helper_rsa.c b/src/util/crypto_helper_rsa.c
index fbfc97702..e23e12a88 100644
--- a/src/util/crypto_helper_rsa.c
+++ b/src/util/crypto_helper_rsa.c
@@ -14,7 +14,7 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file util/crypto_helper_denom.c
+ * @file util/crypto_helper_rsa.c
* @brief utility functions for running out-of-process private key operations
* @author Christian Grothoff
*/
@@ -113,21 +113,28 @@ try_connect (struct TALER_CRYPTO_RsaDenominationHelper *dh)
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,
- "taler-exchange-secmod-rsa",
+ secname,
"UNIXPATH",
&unixpath))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "taler-exchange-secmod-rsa",
+ secname,
"UNIXPATH");
+ GNUNET_free (secname);
return NULL;
}
/* we use >= here because we want the sun_path to always
@@ -135,12 +142,14 @@ TALER_CRYPTO_helper_rsa_connect (
if (strlen (unixpath) >= sizeof (dh->sa.sun_path))
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "taler-exchange-secmod-rsa",
+ 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;
@@ -203,23 +212,27 @@ handle_mt_avail (struct TALER_CRYPTO_RsaDenominationHelper *dh,
}
{
- struct TALER_DenominationPublicKey denom_pub;
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub;
struct TALER_RsaPubHashP h_rsa;
- denom_pub.cipher = TALER_DENOMINATION_RSA;
- denom_pub.details.rsa_public_key
+ 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 == denom_pub.details.rsa_public_key)
+ if (NULL == bs_pub->details.rsa_public_key)
{
GNUNET_break_op (0);
+ GNUNET_free (bs_pub);
return GNUNET_SYSERR;
}
- GNUNET_CRYPTO_rsa_public_key_hash (denom_pub.details.rsa_public_key,
- &h_rsa.hash);
+ 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 (&h_rsa.hash),
+ GNUNET_h2s (&bs_pub->pub_key_hash),
section_name);
if (GNUNET_OK !=
TALER_exchange_secmod_rsa_verify (
@@ -231,7 +244,7 @@ handle_mt_avail (struct TALER_CRYPTO_RsaDenominationHelper *dh,
&kan->secm_sig))
{
GNUNET_break_op (0);
- TALER_denom_pub_free (&denom_pub);
+ GNUNET_CRYPTO_blind_sign_pub_decref (bs_pub);
return GNUNET_SYSERR;
}
dh->dkc (dh->dkc_cls,
@@ -239,10 +252,10 @@ handle_mt_avail (struct TALER_CRYPTO_RsaDenominationHelper *dh,
GNUNET_TIME_timestamp_ntoh (kan->anchor_time),
GNUNET_TIME_relative_ntoh (kan->duration_withdraw),
&h_rsa,
- &denom_pub,
+ bs_pub,
&kan->secm_pub,
&kan->secm_sig);
- TALER_denom_pub_free (&denom_pub);
+ GNUNET_CRYPTO_blind_sign_pub_decref (bs_pub);
}
return GNUNET_OK;
}
@@ -387,18 +400,17 @@ more:
}
-struct TALER_BlindedDenominationSignature
+enum TALER_ErrorCode
TALER_CRYPTO_helper_rsa_sign (
struct TALER_CRYPTO_RsaDenominationHelper *dh,
- const struct TALER_RsaPubHashP *h_rsa,
- const void *msg,
- size_t msg_size,
- enum TALER_ErrorCode *ec)
+ const struct TALER_CRYPTO_RsaSignRequest *rsr,
+ struct TALER_BlindedDenominationSignature *bs)
{
- struct TALER_BlindedDenominationSignature ds = {
- .cipher = TALER_DENOMINATION_INVALID
- };
+ 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 !=
@@ -406,24 +418,23 @@ TALER_CRYPTO_helper_rsa_sign (
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to connect to helper\n");
- *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
- return ds;
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Requesting signature\n");
{
- char buf[sizeof (struct TALER_CRYPTO_SignRequest) + msg_size];
+ 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 = *h_rsa;
- memcpy (&sr[1],
- msg,
- msg_size);
+ 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,
@@ -432,8 +443,7 @@ TALER_CRYPTO_helper_rsa_sign (
GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
"send");
do_disconnect (dh);
- *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
- return ds;
+ return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
}
}
@@ -446,7 +456,6 @@ TALER_CRYPTO_helper_rsa_sign (
= (const struct GNUNET_MessageHeader *) buf;
bool finished = false;
- *ec = TALER_EC_INVALID;
while (1)
{
uint16_t msize;
@@ -466,20 +475,20 @@ TALER_CRYPTO_helper_rsa_sign (
{
GNUNET_assert (finished);
GNUNET_assert (0 == off);
- return ds;
+ return ec;
}
GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
"recv");
do_disconnect (dh);
- *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE;
+ 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 ds;
+ ec = TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+ return ec;
}
off += ret;
more:
@@ -495,20 +504,21 @@ more:
{
GNUNET_break_op (0);
do_disconnect (dh);
- *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ 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;
+ 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],
@@ -517,15 +527,18 @@ more:
{
GNUNET_break_op (0);
do_disconnect (dh);
- *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
goto end;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Received signature\n");
- *ec = TALER_EC_NONE;
+ ec = TALER_EC_NONE;
finished = true;
- ds.cipher = TALER_DENOMINATION_RSA;
- ds.details.blinded_rsa_signature = rsa_signature;
+ 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:
@@ -533,14 +546,14 @@ more:
{
GNUNET_break_op (0);
do_disconnect (dh);
- *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ 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);
+ ec = (enum TALER_ErrorCode) ntohl (sf->ec);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Signing failed!\n");
finished = true;
@@ -555,7 +568,7 @@ more:
{
GNUNET_break_op (0);
do_disconnect (dh);
- *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
goto end;
}
break; /* while(1) loop ensures we recvfrom() again */
@@ -568,7 +581,7 @@ more:
{
GNUNET_break_op (0);
do_disconnect (dh);
- *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
goto end;
}
break; /* while(1) loop ensures we recvfrom() again */
@@ -583,7 +596,7 @@ more:
"Received unexpected message of type %u\n",
ntohs (hdr->type));
do_disconnect (dh);
- *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
+ ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG;
goto end;
}
memmove (buf,
@@ -594,9 +607,269 @@ more:
} /* while(1) */
end:
if (finished)
- TALER_blinded_denom_sig_free (&ds);
- return ds;
+ 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;
}
diff --git a/src/util/crypto_wire.c b/src/util/crypto_wire.c
index e1c7d9646..aa504b81e 100644
--- a/src/util/crypto_wire.c
+++ b/src/util/crypto_wire.c
@@ -23,49 +23,10 @@
#include "taler_signatures.h"
-enum GNUNET_GenericReturnValue
-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 = {
- .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_DETAILS),
- .purpose.size = htonl (sizeof (wd))
- };
-
- TALER_payto_hash (payto_uri,
- &wd.h_wire_details);
- 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 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);
- GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
- &wd,
- &master_sig->eddsa_signature);
-}
-
-
void
TALER_merchant_wire_signature_hash (const char *payto_uri,
- const struct TALER_WireSalt *salt,
- struct TALER_MerchantWireHash *hc)
+ const struct TALER_WireSaltP *salt,
+ struct TALER_MerchantWireHashP *hc)
{
GNUNET_assert (GNUNET_YES ==
GNUNET_CRYPTO_kdf (hc,
@@ -80,47 +41,4 @@ TALER_merchant_wire_signature_hash (const char *payto_uri,
}
-enum GNUNET_GenericReturnValue
-TALER_merchant_wire_signature_check (
- const char *payto_uri,
- const struct TALER_WireSalt *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_WireSalt *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);
-}
-
-
/* 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
index b6b3764da..cb232c4a3 100644
--- a/src/util/denom.c
+++ b/src/util/denom.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ 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
@@ -25,87 +25,43 @@
enum GNUNET_GenericReturnValue
TALER_denom_priv_create (struct TALER_DenominationPrivateKey *denom_priv,
struct TALER_DenominationPublicKey *denom_pub,
- enum TALER_DenominationCipher cipher,
+ enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher,
...)
{
- memset (denom_priv,
- 0,
- sizeof (*denom_priv));
+ enum GNUNET_GenericReturnValue ret;
+ va_list ap;
+
memset (denom_pub,
0,
sizeof (*denom_pub));
- switch (cipher)
- {
- case TALER_DENOMINATION_INVALID:
- GNUNET_break (0);
- return GNUNET_SYSERR;
- case TALER_DENOMINATION_RSA:
- {
- va_list ap;
- unsigned int bits;
-
- va_start (ap, cipher);
- bits = va_arg (ap, unsigned int);
- va_end (ap);
- if (bits < 512)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- denom_priv->details.rsa_private_key
- = GNUNET_CRYPTO_rsa_private_key_create (bits);
- }
- if (NULL == denom_priv->details.rsa_private_key)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- denom_pub->details.rsa_public_key
- = GNUNET_CRYPTO_rsa_private_key_get_public (
- denom_priv->details.rsa_private_key);
- denom_priv->cipher = cipher;
- denom_pub->cipher = cipher;
- return GNUNET_OK;
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_break (0);
- }
- return GNUNET_SYSERR;
+ 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,
- void *blinded_msg,
- size_t blinded_msg_size)
+ bool for_melt,
+ const struct TALER_BlindedPlanchet *blinded_planchet)
{
- memset (denom_sig,
- 0,
- sizeof (*denom_sig));
- switch (denom_priv->cipher)
- {
- case TALER_DENOMINATION_INVALID:
- GNUNET_break (0);
+ 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;
- case TALER_DENOMINATION_RSA:
- denom_sig->details.blinded_rsa_signature
- = GNUNET_CRYPTO_rsa_sign_blinded (
- denom_priv->details.rsa_private_key,
- blinded_msg,
- blinded_msg_size);
- if (NULL == denom_sig->details.blinded_rsa_signature)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- denom_sig->cipher = TALER_DENOMINATION_RSA;
- return GNUNET_OK;
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_break (0);
- }
- return GNUNET_SYSERR;
+ return GNUNET_OK;
}
@@ -113,88 +69,52 @@ enum GNUNET_GenericReturnValue
TALER_denom_sig_unblind (
struct TALER_DenominationSignature *denom_sig,
const struct TALER_BlindedDenominationSignature *bdenom_sig,
- const union TALER_DenominationBlindingKeyP *bks,
+ const union GNUNET_CRYPTO_BlindingSecretP *bks,
+ const struct TALER_CoinPubHashP *c_hash,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
const struct TALER_DenominationPublicKey *denom_pub)
{
- if (bdenom_sig->cipher != denom_pub->cipher)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- switch (denom_pub->cipher)
+ 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)
{
- case TALER_DENOMINATION_INVALID:
- GNUNET_break (0);
+ GNUNET_break_op (0);
return GNUNET_SYSERR;
- case TALER_DENOMINATION_RSA:
- denom_sig->details.rsa_signature
- = GNUNET_CRYPTO_rsa_unblind (
- bdenom_sig->details.blinded_rsa_signature,
- &bks->rsa_bks,
- denom_pub->details.rsa_public_key);
- if (NULL == denom_sig->details.rsa_signature)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- denom_sig->cipher = TALER_DENOMINATION_RSA;
- return GNUNET_OK;
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_break (0);
}
- return GNUNET_SYSERR;
-}
-
-
-void
-TALER_blinding_secret_create (union TALER_DenominationBlindingKeyP *bs)
-{
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
- bs,
- sizeof (*bs));
-}
-
-
-/**
- * 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)
-{
- GNUNET_CRYPTO_rsa_public_key_hash (rsa,
- &h_rsa->hash);
-
+ return GNUNET_OK;
}
void
TALER_denom_pub_hash (const struct TALER_DenominationPublicKey *denom_pub,
- struct TALER_DenominationHash *denom_hash)
+ struct TALER_DenominationHashP *denom_hash)
{
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bsp
+ = denom_pub->bsign_pub_key;
uint32_t opt[2] = {
- htonl (denom_pub->age_mask.mask),
- htonl ((uint32_t) denom_pub->cipher)
+ 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 (denom_pub->cipher)
+ switch (bsp->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
{
void *buf;
size_t blen;
blen = GNUNET_CRYPTO_rsa_public_key_encode (
- denom_pub->details.rsa_public_key,
+ bsp->details.rsa_public_key,
&buf);
GNUNET_CRYPTO_hash_context_read (hc,
buf,
@@ -202,7 +122,11 @@ TALER_denom_pub_hash (const struct TALER_DenominationPublicKey *denom_pub,
GNUNET_free (buf);
}
break;
- // TODO: add case for Clause-Schnorr
+ 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);
}
@@ -211,112 +135,65 @@ TALER_denom_pub_hash (const struct TALER_DenominationPublicKey *denom_pub,
}
-void
-TALER_denom_priv_to_pub (const struct TALER_DenominationPrivateKey *denom_priv,
- const struct TALER_AgeMask age_mask,
- struct TALER_DenominationPublicKey *denom_pub)
+const struct TALER_ExchangeWithdrawValues *
+TALER_denom_ewv_rsa_singleton ()
{
- switch (denom_priv->cipher)
- {
- case TALER_DENOMINATION_RSA:
- denom_pub->cipher = TALER_DENOMINATION_RSA;
- denom_pub->age_mask = age_mask;
- denom_pub->details.rsa_public_key
- = GNUNET_CRYPTO_rsa_private_key_get_public (
- denom_priv->details.rsa_private_key);
- return;
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_assert (0);
- }
+ 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 TALER_DenominationBlindingKeyP *coin_bks,
- const struct TALER_AgeHash *age_commitment_hash,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- struct TALER_CoinPubHash *c_hash,
- void **coin_ev,
- size_t *coin_ev_size)
+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)
{
- switch (dk->cipher)
- {
- case TALER_DENOMINATION_RSA:
- TALER_coin_pub_hash (coin_pub,
- age_commitment_hash,
- c_hash);
- if (GNUNET_YES !=
- GNUNET_CRYPTO_rsa_blind (&c_hash->hash,
- &coin_bks->rsa_bks,
- dk->details.rsa_public_key,
- coin_ev,
- coin_ev_size))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_break (0);
+ 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_CoinPubHash *c_hash)
+ const struct TALER_CoinPubHashP *c_hash)
{
- if (denom_pub->cipher != denom_sig->cipher)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- switch (denom_pub->cipher)
- {
- case TALER_DENOMINATION_INVALID:
- GNUNET_break (0);
- return GNUNET_NO;
- case TALER_DENOMINATION_RSA:
- if (GNUNET_OK !=
- GNUNET_CRYPTO_rsa_verify (&c_hash->hash,
- denom_sig->details.rsa_signature,
- denom_pub->details.rsa_public_key))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Coin signature is invalid\n");
- return GNUNET_NO;
- }
- return GNUNET_YES;
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_assert (0);
- }
+ 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)
{
- switch (denom_pub->cipher)
+ if (NULL != denom_pub->bsign_pub_key)
{
- case TALER_DENOMINATION_INVALID:
- return;
- case TALER_DENOMINATION_RSA:
- if (NULL != denom_pub->details.rsa_public_key)
- {
- GNUNET_CRYPTO_rsa_public_key_free (denom_pub->details.rsa_public_key);
- denom_pub->details.rsa_public_key = NULL;
- }
- denom_pub->cipher = TALER_DENOMINATION_INVALID;
- return;
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_assert (0);
+ GNUNET_CRYPTO_blind_sign_pub_decref (denom_pub->bsign_pub_key);
+ denom_pub->bsign_pub_key = NULL;
}
}
@@ -324,21 +201,10 @@ TALER_denom_pub_free (struct TALER_DenominationPublicKey *denom_pub)
void
TALER_denom_priv_free (struct TALER_DenominationPrivateKey *denom_priv)
{
- switch (denom_priv->cipher)
+ if (NULL != denom_priv->bsign_priv_key)
{
- case TALER_DENOMINATION_INVALID:
- return;
- case TALER_DENOMINATION_RSA:
- if (NULL != denom_priv->details.rsa_private_key)
- {
- GNUNET_CRYPTO_rsa_private_key_free (denom_priv->details.rsa_private_key);
- denom_priv->details.rsa_private_key = NULL;
- }
- denom_priv->cipher = TALER_DENOMINATION_INVALID;
- return;
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_assert (0);
+ GNUNET_CRYPTO_blind_sign_priv_decref (denom_priv->bsign_priv_key);
+ denom_priv->bsign_priv_key = NULL;
}
}
@@ -346,21 +212,10 @@ TALER_denom_priv_free (struct TALER_DenominationPrivateKey *denom_priv)
void
TALER_denom_sig_free (struct TALER_DenominationSignature *denom_sig)
{
- switch (denom_sig->cipher)
+ if (NULL != denom_sig->unblinded_sig)
{
- case TALER_DENOMINATION_INVALID:
- return;
- case TALER_DENOMINATION_RSA:
- if (NULL != denom_sig->details.rsa_signature)
- {
- GNUNET_CRYPTO_rsa_signature_free (denom_sig->details.rsa_signature);
- denom_sig->details.rsa_signature = NULL;
- }
- denom_sig->cipher = TALER_DENOMINATION_INVALID;
- return;
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_assert (0);
+ GNUNET_CRYPTO_unblinded_sig_decref (denom_sig->unblinded_sig);
+ denom_sig->unblinded_sig = NULL;
}
}
@@ -369,92 +224,73 @@ void
TALER_blinded_denom_sig_free (
struct TALER_BlindedDenominationSignature *denom_sig)
{
- switch (denom_sig->cipher)
+ if (NULL != denom_sig->blinded_sig)
{
- case TALER_DENOMINATION_INVALID:
- return;
- case TALER_DENOMINATION_RSA:
- if (NULL != denom_sig->details.blinded_rsa_signature)
- {
- GNUNET_CRYPTO_rsa_signature_free (
- denom_sig->details.blinded_rsa_signature);
- denom_sig->details.blinded_rsa_signature = NULL;
- }
- denom_sig->cipher = TALER_DENOMINATION_INVALID;
- return;
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_assert (0);
+ GNUNET_CRYPTO_blinded_sig_decref (denom_sig->blinded_sig);
+ denom_sig->blinded_sig = NULL;
}
}
-/**
- * Make a (deep) copy of the given @a denom_src to
- * @a denom_dst.
- *
- * @param[out] denom_dst target to copy to
- * @param denom_str public key to copy
- */
void
-TALER_denom_pub_deep_copy (struct TALER_DenominationPublicKey *denom_dst,
- const struct TALER_DenominationPublicKey *denom_src)
+TALER_denom_ewv_free (struct TALER_ExchangeWithdrawValues *ewv)
{
- *denom_dst = *denom_src; /* shallow copy */
- switch (denom_src->cipher)
+ if (ewv == TALER_denom_ewv_rsa_singleton ())
+ return;
+ if (ewv->blinding_inputs ==
+ TALER_denom_ewv_rsa_singleton ()->blinding_inputs)
{
- case TALER_DENOMINATION_RSA:
- denom_dst->details.rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_dup (
- denom_src->details.rsa_public_key);
+ ewv->blinding_inputs = NULL;
return;
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_assert (0);
+ }
+ if (NULL != ewv->blinding_inputs)
+ {
+ GNUNET_CRYPTO_blinding_input_values_decref (ewv->blinding_inputs);
+ ewv->blinding_inputs = NULL;
}
}
void
-TALER_denom_sig_deep_copy (struct TALER_DenominationSignature *denom_dst,
- const struct TALER_DenominationSignature *denom_src)
+TALER_denom_ewv_copy (struct TALER_ExchangeWithdrawValues *bi_dst,
+ const struct TALER_ExchangeWithdrawValues *bi_src)
{
- *denom_dst = *denom_src; /* shallow copy */
- switch (denom_src->cipher)
+ if (bi_src == TALER_denom_ewv_rsa_singleton ())
{
- case TALER_DENOMINATION_INVALID:
+ *bi_dst = *bi_src;
return;
- case TALER_DENOMINATION_RSA:
- denom_dst->details.rsa_signature
- = GNUNET_CRYPTO_rsa_signature_dup (
- denom_src->details.rsa_signature);
- return;
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_assert (0);
}
+ 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_blinded_denom_sig_deep_copy (
+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 = *denom_src; /* shallow copy */
- switch (denom_src->cipher)
- {
- case TALER_DENOMINATION_INVALID:
- return;
- case TALER_DENOMINATION_RSA:
- denom_dst->details.blinded_rsa_signature
- = GNUNET_CRYPTO_rsa_signature_dup (
- denom_src->details.blinded_rsa_signature);
- return;
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_assert (0);
- }
+ denom_dst->blinded_sig
+ = GNUNET_CRYPTO_blind_sig_incref (denom_src->blinded_sig);
}
@@ -462,22 +298,14 @@ int
TALER_denom_pub_cmp (const struct TALER_DenominationPublicKey *denom1,
const struct TALER_DenominationPublicKey *denom2)
{
- if (denom1->cipher != denom2->cipher)
- return (denom1->cipher > denom2->cipher) ? 1 : -1;
- if (denom1->age_mask.mask != denom2->age_mask.mask)
- return (denom1->age_mask.mask > denom2->age_mask.mask) ? 1 : -1;
- switch (denom1->cipher)
- {
- case TALER_DENOMINATION_INVALID:
- return 0;
- case TALER_DENOMINATION_RSA:
- return GNUNET_CRYPTO_rsa_public_key_cmp (denom1->details.rsa_public_key,
- denom2->details.rsa_public_key);
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_assert (0);
- }
- return -2;
+ 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);
}
@@ -485,20 +313,18 @@ int
TALER_denom_sig_cmp (const struct TALER_DenominationSignature *sig1,
const struct TALER_DenominationSignature *sig2)
{
- if (sig1->cipher != sig2->cipher)
- return (sig1->cipher > sig2->cipher) ? 1 : -1;
- switch (sig1->cipher)
- {
- case TALER_DENOMINATION_INVALID:
- return 0;
- case TALER_DENOMINATION_RSA:
- return GNUNET_CRYPTO_rsa_signature_cmp (sig1->details.rsa_signature,
- sig2->details.rsa_signature);
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_assert (0);
- }
- return -2;
+ 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);
}
@@ -507,20 +333,140 @@ TALER_blinded_denom_sig_cmp (
const struct TALER_BlindedDenominationSignature *sig1,
const struct TALER_BlindedDenominationSignature *sig2)
{
- if (sig1->cipher != sig2->cipher)
- return (sig1->cipher > sig2->cipher) ? 1 : -1;
- switch (sig1->cipher)
+ 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 TALER_DENOMINATION_INVALID:
- return 0;
- case TALER_DENOMINATION_RSA:
- return GNUNET_CRYPTO_rsa_signature_cmp (sig1->details.blinded_rsa_signature,
- sig2->details.blinded_rsa_signature);
- // TODO: add case for Clause-Schnorr
- default:
- GNUNET_assert (0);
+ 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;
}
- return -2;
}
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
index b923c29de..aaefb5cec 100644
--- a/src/util/exchange_signatures.c
+++ b/src/util/exchange_signatures.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -23,16 +23,136 @@
#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_deposit_confirm_verify (
- const struct TALER_PrivateContractHash *h_contract_terms,
- const struct TALER_MerchantWireHash *h_wire,
- const struct TALER_ExtensionContractHash *h_extensions,
+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 *amount_without_fee,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ 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)
@@ -45,14 +165,21 @@ TALER_exchange_deposit_confirm_verify (
.exchange_timestamp = GNUNET_TIME_timestamp_hton (exchange_timestamp),
.wire_deadline = GNUNET_TIME_timestamp_hton (wire_deadline),
.refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline),
- .coin_pub = *coin_pub,
.merchant_pub = *merchant_pub
};
+ struct GNUNET_HashContext *hc;
- if (NULL != h_extensions)
- dcs.h_extensions = *h_extensions;
- TALER_amount_hton (&dcs.amount_without_fee,
- amount_without_fee);
+ 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,
@@ -66,4 +193,1702 @@ TALER_exchange_deposit_confirm_verify (
}
+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/extension_age_restriction.c b/src/util/extension_age_restriction.c
deleted file mode 100644
index b29a8ca88..000000000
--- a/src/util/extension_age_restriction.c
+++ /dev/null
@@ -1,182 +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 extension_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"
-
-/**
- *
- * @param cfg Handle to the GNUNET configuration
- * @param[out] Mask for age restriction. Will be 0 if age restriction was not enabled in the config.
- * @return Error if extension for age restriction was set, but age groups were
- * invalid, OK otherwise.
- */
-enum TALER_Extension_ReturnValue
-TALER_get_age_mask (const struct GNUNET_CONFIGURATION_Handle *cfg,
- struct TALER_AgeMask *mask)
-{
- char *groups;
- enum TALER_Extension_ReturnValue ret = TALER_Extension_ERROR_SYS;
-
- 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")))
- {
- /* Age restriction is not enabled */
- mask->mask = 0;
- return TALER_Extension_OK;
- }
-
- /* Age restriction is enabled, extract age groups */
- if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg,
- TALER_EXTENSION_SECTION_AGE_RESTRICTION,
- "AGE_GROUPS",
- &groups))
- {
- /* FIXME: log error? */
- return TALER_Extension_ERROR_SYS;
- }
- if (groups == NULL)
- {
- /* No groups defined in config, return default_age_mask */
- mask->mask = TALER_EXTENSION_DEFAULT_AGE_MASK;
- return TALER_Extension_OK;
- }
-
- ret = TALER_parse_age_group_string (groups, mask);
- GNUNET_free (groups);
- return ret;
-}
-
-
-/**
- * @param groups String representation of the age groups. Must be of the form
- * a:b:...:n:m
- * with
- * 0 < a < b <...< n < m < 32
- * @param[out] mask Bit representation of the age groups.
- * @return Error if string was invalid, OK otherwise.
- */
-enum TALER_Extension_ReturnValue
-TALER_parse_age_group_string (const char *groups,
- struct TALER_AgeMask *mask)
-{
- enum TALER_Extension_ReturnValue ret = TALER_Extension_ERROR_SYS;
- char *pos;
- unsigned int prev = 0;
- unsigned int val;
- char dummy;
-
- while (1)
- {
- pos = strchr (groups, ':');
- if (NULL != pos)
- {
- *pos = 0;
- }
-
- if (1 != sscanf (groups,
- "%u%c",
- &val,
- &dummy))
- {
- /* Invalid input */
- mask->mask = 0;
- ret = TALER_Extension_ERROR_PARSING;
- break;
- }
- else if ((0 >= val) || (32 <= val) || (prev >= val))
- {
- /* Invalid value */
- mask->mask = 0;
- ret = TALER_Extension_ERROR_INVALID;
- break;
- }
-
- /* Set the corresponding bit in the mask */
- mask->mask |= 1 << val;
-
- if (NULL == pos)
- {
- /* We reached the end. Mark zeroth age-group and exit. */
- mask->mask |= 1;
- ret = TALER_Extension_OK;
- break;
- }
-
- prev = val;
- *pos = ':';
- groups = pos + 1;
- }
-
- return ret;
-}
-
-
-/**
- * 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 *
-TALER_age_mask_to_string (const struct TALER_AgeMask *m)
-{
- uint32_t mask = m->mask;
- unsigned int n = 0;
- char *buf = GNUNET_malloc (32 * 3); // max characters possible
- char *pos = buf;
-
- if (NULL == buf)
- {
- return buf;
- }
-
- while (mask != 0)
- {
- mask >>= 1;
- n++;
- if (0 == (mask & 1))
- {
- continue;
- }
-
- if (n > 9)
- {
- *(pos++) = '0' + n / 10;
- }
- *(pos++) = '0' + n % 10;
-
- if (0 != (mask >> 1))
- {
- *(pos++) = ':';
- }
- }
- return buf;
-}
-
-
-/* end of extension_age_restriction.c */
diff --git a/src/util/iban.c b/src/util/iban.c
index efd8c4282..c2274d3cb 100644
--- a/src/util/iban.c
+++ b/src/util/iban.c
@@ -233,9 +233,9 @@ TALER_iban_validate (const char *iban)
return GNUNET_strdup ("IBAN number too short to be valid");
if (len > 34)
return GNUNET_strdup ("IBAN number too long to be valid");
- memcpy (cc, iban, 2);
- memcpy (ibancpy, iban + 4, len - 4);
- memcpy (ibancpy + len - 4, iban, 4);
+ 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;
diff --git a/src/util/merchant_signatures.c b/src/util/merchant_signatures.c
index ae1769552..35e0b0e07 100644
--- a/src/util/merchant_signatures.c
+++ b/src/util/merchant_signatures.c
@@ -23,10 +23,126 @@
#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_PrivateContractHash *h_contract_terms,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
uint64_t rtransaction_id,
const struct TALER_Amount *amount,
const struct TALER_MerchantPrivateKeyP *merchant_priv,
@@ -51,7 +167,7 @@ TALER_merchant_refund_sign (
enum GNUNET_GenericReturnValue
TALER_merchant_refund_verify (
const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_PrivateContractHash *h_contract_terms,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
uint64_t rtransaction_id,
const struct TALER_Amount *amount,
const struct TALER_MerchantPublicKeyP *merchant_pub,
@@ -75,4 +191,162 @@ TALER_merchant_refund_verify (
}
+/**
+ * @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/offline_signatures.c b/src/util/offline_signatures.c
index 1240a8bc5..fbff850df 100644
--- a/src/util/offline_signatures.c
+++ b/src/util/offline_signatures.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020, 2021 Taler Systems SA
+ Copyright (C) 2020-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -23,6 +23,132 @@
#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,
@@ -73,6 +199,35 @@ TALER_exchange_offline_auditor_add_verify (
}
+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,
@@ -115,9 +270,31 @@ TALER_exchange_offline_auditor_del_verify (
}
+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_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_MasterPrivateKeyP *master_priv,
struct TALER_MasterSignatureP *master_sig)
{
@@ -135,7 +312,7 @@ TALER_exchange_offline_denomination_revoke_sign (
enum GNUNET_GenericReturnValue
TALER_exchange_offline_denomination_revoke_verify (
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_MasterPublicKeyP *master_pub,
const struct TALER_MasterSignatureP *master_sig)
{
@@ -154,6 +331,28 @@ TALER_exchange_offline_denomination_revoke_verify (
}
+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,
@@ -194,6 +393,55 @@ TALER_exchange_offline_signkey_revoke_verify (
}
+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,
@@ -247,18 +495,100 @@ TALER_exchange_offline_signkey_validity_verify (
}
+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_DenominationHash *h_denom_pub,
+ 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_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,
const struct TALER_MasterPrivateKeyP *master_priv,
struct TALER_MasterSignatureP *master_sig)
{
@@ -278,14 +608,8 @@ TALER_exchange_offline_denom_validity_sign (
&issue.master.eddsa_pub);
TALER_amount_hton (&issue.value,
coin_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);
+ TALER_denom_fee_set_hton (&issue.fees,
+ fees);
GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
&issue,
&master_sig->eddsa_signature);
@@ -294,16 +618,13 @@ TALER_exchange_offline_denom_validity_sign (
enum GNUNET_GenericReturnValue
TALER_exchange_offline_denom_validity_verify (
- const struct TALER_DenominationHash *h_denom_pub,
+ 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_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,
const struct TALER_MasterPublicKeyP *master_pub,
const struct TALER_MasterSignatureP *master_sig)
{
@@ -321,14 +642,8 @@ TALER_exchange_offline_denom_validity_verify (
TALER_amount_hton (&dkv.value,
coin_value);
- TALER_amount_hton (&dkv.fee_withdraw,
- fee_withdraw);
- TALER_amount_hton (&dkv.fee_deposit,
- fee_deposit);
- TALER_amount_hton (&dkv.fee_refresh,
- fee_refresh);
- TALER_amount_hton (&dkv.fee_refund,
- fee_refund);
+ TALER_denom_fee_set_hton (&dkv.fees,
+ fees);
return
GNUNET_CRYPTO_eddsa_verify (
TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY,
@@ -338,9 +653,57 @@ TALER_exchange_offline_denom_validity_verify (
}
+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)
@@ -353,6 +716,14 @@ TALER_exchange_offline_wire_add_sign (
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);
@@ -362,6 +733,9 @@ TALER_exchange_offline_wire_add_sign (
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)
@@ -374,6 +748,14 @@ TALER_exchange_offline_wire_add_verify (
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,
@@ -383,6 +765,36 @@ TALER_exchange_offline_wire_add_verify (
}
+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,
@@ -428,13 +840,54 @@ TALER_exchange_offline_wire_del_verify (
}
+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_Amount *wire_fee,
- const struct TALER_Amount *closing_fee,
+ const struct TALER_WireFeeSet *fees,
const struct TALER_MasterPrivateKeyP *master_priv,
struct TALER_MasterSignatureP *master_sig)
{
@@ -448,10 +901,8 @@ TALER_exchange_offline_wire_fee_sign (
GNUNET_CRYPTO_hash (payment_method,
strlen (payment_method) + 1,
&kv.h_wire_method);
- TALER_amount_hton (&kv.wire_fee,
- wire_fee);
- TALER_amount_hton (&kv.closing_fee,
- closing_fee);
+ TALER_wire_fee_set_hton (&kv.fees,
+ fees);
GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
&kv,
&master_sig->eddsa_signature);
@@ -463,8 +914,7 @@ 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_Amount *wire_fee,
- const struct TALER_Amount *closing_fee,
+ const struct TALER_WireFeeSet *fees,
const struct TALER_MasterPublicKeyP *master_pub,
const struct TALER_MasterSignatureP *master_sig)
{
@@ -478,10 +928,8 @@ TALER_exchange_offline_wire_fee_verify (
GNUNET_CRYPTO_hash (payment_method,
strlen (payment_method) + 1,
&wf.h_wire_method);
- TALER_amount_hton (&wf.wire_fee,
- wire_fee);
- TALER_amount_hton (&wf.closing_fee,
- closing_fee);
+ TALER_wire_fee_set_hton (&wf.fees,
+ fees);
return
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_WIRE_FEES,
&wf,
@@ -490,16 +938,155 @@ TALER_exchange_offline_wire_fee_verify (
}
+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_config_hash_sign (
- const struct TALER_ExtensionConfigHash h_config,
+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_MasterExtensionConfigurationPS ec = {
+ struct TALER_MasterExtensionManifestPS ec = {
.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION),
.purpose.size = htonl (sizeof(ec)),
- .h_config = h_config
+ .h_manifest = *h_manifest
};
GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
&ec,
@@ -508,16 +1095,16 @@ TALER_exchange_offline_extension_config_hash_sign (
enum GNUNET_GenericReturnValue
-TALER_exchange_offline_extension_config_hash_verify (
- const struct TALER_ExtensionConfigHash h_config,
+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_MasterExtensionConfigurationPS ec = {
+ struct TALER_MasterExtensionManifestPS ec = {
.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION),
.purpose.size = htonl (sizeof(ec)),
- .h_config = h_config
+ .h_manifest = *h_manifest
};
return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_EXTENSION,
@@ -527,4 +1114,275 @@ TALER_exchange_offline_extension_config_hash_verify (
}
+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/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 746b2624d..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-2021 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
@@ -101,22 +101,40 @@ TALER_payto_get_method (const char *payto_uri)
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);
}
@@ -143,7 +161,6 @@ validate_payto_iban (const char *account_url)
IBAN_PREFIX,
strlen (IBAN_PREFIX)))
return NULL; /* not an IBAN */
-
iban = strrchr (account_url, '/') + 1;
#undef IBAN_PREFIX
q = strchr (iban,
@@ -177,6 +194,138 @@ validate_payto_iban (const char *account_url)
}
+/**
+ * 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)
{
@@ -193,7 +342,7 @@ TALER_payto_validate (const char *payto_uri)
/* 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/:&?-.,="
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:&?-.,=+%~"
if (NULL == strchr (ALLOWED_CHARACTERS,
(int) payto_uri[i]))
{
@@ -217,6 +366,8 @@ TALER_payto_validate (const char *payto_uri)
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! */
@@ -224,52 +375,320 @@ TALER_payto_validate (const char *payto_uri)
}
+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_PaytoHash *h_payto)
+ struct TALER_PaytoHashP *h_payto)
{
+ struct GNUNET_HashCode sha512;
+
GNUNET_CRYPTO_hash (payto,
strlen (payto) + 1,
- &h_payto->hash);
+ &sha512);
+ GNUNET_static_assert (sizeof (sha512) > sizeof (*h_payto));
+ /* truncate */
+ GNUNET_memcpy (h_payto,
+ &sha512,
+ sizeof (*h_payto));
}
char *
-TALER_payto_from_reserve (const char *exchange_base_url,
+TALER_reserve_make_payto (const char *exchange_url,
const struct TALER_ReservePublicKeyP *reserve_pub)
{
- char *payto_uri;
- char *rps;
- unsigned int skip;
- const char *extra = "";
- int url_len;
-
- rps = GNUNET_STRINGS_data_to_string_alloc (reserve_pub,
- sizeof (*reserve_pub));
- skip = 0;
- if (0 == strncasecmp (exchange_base_url,
- "http://",
- strlen ("http://")))
+ 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://")))
{
- skip = strlen ("http://");
- extra = "+http";
+ is_http = false;
+ exchange_url = &exchange_url[strlen ("https://")];
+ }
+ else
+ {
+ GNUNET_break (0);
+ return NULL;
}
- if (0 == strncasecmp (exchange_base_url,
- "https://",
- strlen ("https://")))
- skip = strlen ("https://");
- url_len = strlen (exchange_base_url);
- if ('/' == exchange_base_url[url_len - 1])
- url_len--;
- url_len -= skip;
- GNUNET_asprintf (&payto_uri,
- "taler%s://reserve/%.*s/%s",
- extra,
- url_len,
- exchange_base_url + skip,
- rps);
- GNUNET_free (rps);
- return payto_uri;
+ /* 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;
}
diff --git a/src/util/secmod_common.c b/src/util/secmod_common.c
index 975bae3f7..87ce17e06 100644
--- a/src/util/secmod_common.c
+++ b/src/util/secmod_common.c
@@ -23,7 +23,9 @@
#include "taler_signatures.h"
#include "secmod_common.h"
#include <poll.h>
+#ifdef __linux__
#include <sys/eventfd.h>
+#endif
/**
@@ -217,7 +219,11 @@ TES_wake_clients (void)
client = client->next)
{
GNUNET_assert (sizeof (num) ==
+#ifdef __linux__
write (client->esock,
+#else
+ write (client->esock_in,
+#endif
&num,
sizeof (num)));
}
@@ -243,7 +249,7 @@ TES_read_work (void *cls,
recv_size = recv (client->csock,
&buf[off],
sizeof (client->iobuf) - off,
- 0);
+ 0);
if (-1 == recv_size)
{
if ( (0 == off) &&
@@ -309,7 +315,11 @@ TES_await_ready (struct TES_Client *client)
.events = POLLIN
},
{
+#ifdef __linux__
.fd = client->esock,
+#else
+ .fd = client->esock_out,
+#endif
.events = POLLIN
},
};
@@ -324,13 +334,21 @@ TES_await_ready (struct TES_Client *client)
"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;
@@ -349,7 +367,12 @@ TES_free_client (struct TES_Client *client)
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);
}
@@ -401,7 +424,11 @@ 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);
@@ -418,6 +445,7 @@ listen_job (void *cls)
"accept");
return;
}
+#ifdef __linux__
e = eventfd (0,
EFD_CLOEXEC);
if (-1 == e)
@@ -427,13 +455,27 @@ listen_job (void *cls)
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,
diff --git a/src/util/secmod_common.h b/src/util/secmod_common.h
index b24e91cb2..304acebdf 100644
--- a/src/util/secmod_common.h
+++ b/src/util/secmod_common.h
@@ -155,10 +155,22 @@ struct TES_Client
*/
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
};
diff --git a/src/util/secmod_signatures.c b/src/util/secmod_signatures.c
index 9cb15bcf5..3b539d5fe 100644
--- a/src/util/secmod_signatures.c
+++ b/src/util/secmod_signatures.c
@@ -23,6 +23,37 @@
#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,
@@ -69,6 +100,41 @@ TALER_exchange_secmod_eddsa_verify (
}
+/**
+ * @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,
@@ -81,7 +147,7 @@ TALER_exchange_secmod_rsa_sign (
struct TALER_DenominationKeyAnnouncementPS dka = {
.purpose.purpose = htonl (TALER_SIGNATURE_SM_RSA_DENOMINATION_KEY),
.purpose.size = htonl (sizeof (dka)),
- .h_rsa = *h_rsa,
+ .h_denom.hash = h_rsa->hash,
.anchor_time = GNUNET_TIME_timestamp_hton (start_sign),
.duration_withdraw = GNUNET_TIME_relative_hton (duration)
};
@@ -108,7 +174,7 @@ TALER_exchange_secmod_rsa_verify (
struct TALER_DenominationKeyAnnouncementPS dka = {
.purpose.purpose = htonl (TALER_SIGNATURE_SM_RSA_DENOMINATION_KEY),
.purpose.size = htonl (sizeof (dka)),
- .h_rsa = *h_rsa,
+ .h_denom.hash = h_rsa->hash,
.anchor_time = GNUNET_TIME_timestamp_hton (start_sign),
.duration_withdraw = GNUNET_TIME_relative_hton (duration)
};
@@ -124,4 +190,59 @@ TALER_exchange_secmod_rsa_verify (
}
+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.in b/src/util/taler-config.in
index 07f6401d6..3399aec10 100644
--- a/src/util/taler-config.in
+++ b/src/util/taler-config.in
@@ -7,7 +7,7 @@ if ! type gnunet-config >/dev/null; then
exit 1
fi
-GC=`which gnunet-config`
-SO=`ls %libdir%/libtalerutil.so.* | sort -n | tail -n1`
+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
index 02b36fb9a..0b95447f7 100644
--- a/src/util/taler-exchange-secmod-eddsa.c
+++ b/src/util/taler-exchange-secmod-eddsa.c
@@ -37,7 +37,6 @@
#include "taler-exchange-secmod-eddsa.h"
#include <gcrypt.h>
#include <pthread.h>
-#include <sys/eventfd.h>
#include "taler_error_codes.h"
#include "taler_signatures.h"
#include "secmod_common.h"
@@ -138,6 +137,13 @@ static struct GNUNET_TIME_Timestamp now_tmp;
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.
@@ -585,11 +591,11 @@ eddsa_client_init (struct TES_Client *client)
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);
- GNUNET_assert (0 == pthread_mutex_lock (&keys_lock));
for (struct Key *key = keys_head;
NULL != key;
key = key->next)
@@ -827,9 +833,9 @@ parse_key (const char *filename,
filename);
return GNUNET_SYSERR;
}
- memcpy (&priv,
- buf,
- buf_size);
+ GNUNET_memcpy (&priv,
+ buf,
+ buf_size);
{
struct GNUNET_CRYPTO_EddsaPublicKey pub;
@@ -905,7 +911,7 @@ import_key (void *cls,
}
fd = open (filename,
- O_CLOEXEC);
+ O_RDONLY | O_CLOEXEC);
if (-1 == fd)
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
@@ -919,6 +925,7 @@ import_key (void *cls,
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"stat",
filename);
+ GNUNET_break (0 == close (fd));
return GNUNET_OK;
}
if (! S_ISREG (sbuf.st_mode))
@@ -926,6 +933,7 @@ import_key (void *cls,
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)))
@@ -990,39 +998,48 @@ import_key (void *cls,
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,
- "taler-exchange-secmod-eddsa",
+ secname,
"OVERLAP_DURATION",
&overlap_duration))
{
+ GNUNET_free (secname);
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "taler-exchange-secmod-eddsa",
+ secname,
"OVERLAP_DURATION");
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg,
- "taler-exchange-secmod-eddsa",
+ secname,
"DURATION",
&duration))
{
+ GNUNET_free (secname);
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "taler-exchange-secmod-eddsa",
+ secname,
"DURATION");
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg,
- "taler-exchange-secmod-eddsa",
+ secname,
"LOOKAHEAD_SIGN",
&lookahead_sign))
{
+ GNUNET_free (secname);
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "taler-exchange-secmod-eddsa",
+ secname,
"LOOKAHEAD_SIGN");
return GNUNET_SYSERR;
}
+ GNUNET_free (secname);
return GNUNET_OK;
}
@@ -1064,6 +1081,7 @@ run (void *cls,
.updater = eddsa_update_client_keys,
.init = eddsa_client_init
};
+ char *secname;
(void) cls;
(void) args;
@@ -1078,6 +1096,9 @@ run (void *cls,
/* 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))
{
@@ -1086,21 +1107,31 @@ run (void *cls,
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_filename (cfg,
- "taler-exchange-secmod-eddsa",
+ secname,
"KEY_DIR",
&keydir))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "taler-exchange-secmod-eddsa",
+ secname,
"KEY_DIR");
+ GNUNET_free (secname);
global_ret = EXIT_NOTCONFIGURED;
return;
}
+ GNUNET_free (secname);
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
NULL);
- global_ret = TES_listen_start (cfg,
- "taler-exchange-secmod-eddsa",
- &cb);
+ {
+ 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 */
@@ -1143,6 +1174,11 @@ 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',
@@ -1156,7 +1192,7 @@ main (int argc,
/* 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 */
diff --git a/src/util/taler-exchange-secmod-eddsa.conf b/src/util/taler-exchange-secmod-eddsa.conf
index ea09f0334..0cb4a4ffc 100644
--- a/src/util/taler-exchange-secmod-eddsa.conf
+++ b/src/util/taler-exchange-secmod-eddsa.conf
@@ -8,16 +8,16 @@
OVERLAP_DURATION = 5m
# Where do we store the private keys.
-KEY_DIR = ${TALER_DATA_HOME}/exchange-secmod-eddsa/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
+UNIXPATH = ${TALER_RUNTIME_DIR}exchange-secmod-eddsa/server.sock
# Directory for clients.
-CLIENT_DIR = $TALER_RUNTIME_DIR/exchange-secmod-eddsa/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
+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
diff --git a/src/util/taler-exchange-secmod-rsa.c b/src/util/taler-exchange-secmod-rsa.c
index e40c8e39f..c80e2e3c4 100644
--- a/src/util/taler-exchange-secmod-rsa.c
+++ b/src/util/taler-exchange-secmod-rsa.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 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
@@ -36,7 +36,6 @@
#include "taler-exchange-secmod-rsa.h"
#include <gcrypt.h>
#include <pthread.h>
-#include <sys/eventfd.h>
#include "taler_error_codes.h"
#include "taler_signatures.h"
#include "secmod_common.h"
@@ -165,6 +164,128 @@ struct Denomination
/**
+ * 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;
@@ -187,6 +308,13 @@ static struct GNUNET_TIME_Timestamp now_tmp;
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.
@@ -229,11 +357,17 @@ static pthread_mutex_t keys_lock;
*/
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] denomination key to generate the announcement for
+ * @param[in,out] dk denomination key to generate the announcement for
*/
static void
generate_response (struct DenominationKey *dk)
@@ -267,133 +401,453 @@ generate_response (struct DenominationKey *dk)
&an->secm_sig);
an->secm_pub = TES_smpub;
p = (void *) &an[1];
- memcpy (p,
- buf,
- buf_len);
+ GNUNET_memcpy (p,
+ buf,
+ buf_len);
GNUNET_free (buf);
- memcpy (p + buf_len,
- denom->section,
- nlen);
+ GNUNET_memcpy (p + buf_len,
+ denom->section,
+ nlen);
dk->an = an;
}
/**
- * Handle @a client request @a sr to create signature. Create the
- * signature using the respective key and return the result to
- * the client.
+ * Do the actual signing work.
*
- * @param client the client making the request
- * @param sr the request details
- * @return #GNUNET_OK on success
+ * @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 GNUNET_GenericReturnValue
-handle_sign_request (struct TES_Client *client,
- const struct TALER_CRYPTO_SignRequest *sr)
+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;
- const void *blinded_msg = &sr[1];
- size_t blinded_msg_size = ntohs (sr->header.size) - sizeof (*sr);
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,
- &sr->h_rsa.hash);
+ &h_rsa->hash);
if (NULL == dk)
{
- struct TALER_CRYPTO_SignFailure sf = {
- .header.size = htons (sizeof (sr)),
- .header.type = htons (TALER_HELPER_RSA_MT_RES_SIGN_FAILURE),
- .ec = htonl (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN)
- };
-
GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Signing request failed, denomination key %s unknown\n",
- GNUNET_h2s (&sr->h_rsa.hash));
- return TES_transmit (client->csock,
- &sf.header);
+ 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 */
- struct TALER_CRYPTO_SignFailure sf = {
- .header.size = htons (sizeof (sr)),
- .header.type = htons (TALER_HELPER_RSA_MT_RES_SIGN_FAILURE),
- .ec = htonl (TALER_EC_EXCHANGE_DENOMINATION_HELPER_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 (&sr->h_rsa.hash));
- return TES_transmit (client->csock,
- &sf.header);
+ 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) blinded_msg_size,
- GNUNET_h2s (&sr->h_rsa.hash));
+ (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,
- blinded_msg,
- blinded_msg_size);
+ 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_SIGN_FAILURE),
+ .header.type = htons (TALER_HELPER_RSA_MT_RES_BATCH_FAILURE),
.ec = htonl (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE)
};
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Signing request failed, worker failed to produce signature\n");
+ 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))
{
- struct TALER_CRYPTO_SignResponse *sr;
- void *buf;
- size_t buf_size;
- size_t tsize;
- enum GNUNET_GenericReturnValue ret;
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "pthread_create");
+ GNUNET_free (w);
+ return GNUNET_SYSERR;
+ }
+ workers++;
+ return GNUNET_OK;
+}
- 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);
- memcpy (&sr[1],
- buf,
- buf_size);
- GNUNET_free (buf);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Sending RSA signature after %s\n",
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_duration (now),
- GNUNET_YES));
- ret = TES_transmit (client->csock,
- &sr->header);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Sent RSA signature after %s\n",
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_duration (now),
- GNUNET_YES));
- GNUNET_free (sr);
- return ret;
+
+/**
+ * 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--;
}
}
@@ -432,8 +886,8 @@ setup_key (struct DenominationKey *dk,
}
buf_size = GNUNET_CRYPTO_rsa_private_key_encode (priv,
&buf);
- TALER_rsa_pub_hash (pub,
- &dk->h_rsa);
+ GNUNET_CRYPTO_rsa_public_key_hash (pub,
+ &dk->h_rsa.hash);
GNUNET_asprintf (&dk->filename,
"%s/%s/%llu",
keydir,
@@ -607,6 +1061,15 @@ rsa_work_dispatch (struct TES_Client *client,
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;
@@ -639,6 +1102,8 @@ rsa_client_init (struct TES_Client *client)
NULL != dk;
dk = dk->next)
{
+ GNUNET_assert (obs + ntohs (dk->an->header.size)
+ > obs);
obs += ntohs (dk->an->header.size);
}
}
@@ -652,9 +1117,11 @@ rsa_client_init (struct TES_Client *client)
NULL != dk;
dk = dk->next)
{
- memcpy (&buf[obs],
- dk->an,
- ntohs (dk->an->header.size));
+ 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);
}
}
@@ -751,16 +1218,20 @@ rsa_update_client_keys (struct TES_Client *client)
.h_rsa = key->h_rsa
};
- memcpy (&buf[obs],
- &pn,
- sizeof (pn));
+ GNUNET_memcpy (&buf[obs],
+ &pn,
+ sizeof (pn));
+ GNUNET_assert (obs + sizeof (pn)
+ > obs);
obs += sizeof (pn);
}
else
{
- memcpy (&buf[obs],
- key->an,
- ntohs (key->an->header.size));
+ 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);
}
}
@@ -790,6 +1261,7 @@ create_key (struct Denomination *denom,
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;
@@ -1080,8 +1552,8 @@ parse_key (struct Denomination *denom,
dk->denom = denom;
dk->anchor = anchor;
dk->filename = GNUNET_strdup (filename);
- TALER_rsa_pub_hash (pub,
- &dk->h_rsa);
+ GNUNET_CRYPTO_rsa_public_key_hash (pub,
+ &dk->h_rsa.hash);
dk->denom_pub = pub;
generate_response (dk);
if (GNUNET_OK !=
@@ -1106,7 +1578,9 @@ parse_key (struct Denomination *denom,
NULL != pos;
pos = pos->next)
{
- if (GNUNET_TIME_timestamp_cmp (pos->anchor, >, anchor))
+ if (GNUNET_TIME_timestamp_cmp (pos->anchor,
+ >,
+ anchor))
break;
before = pos;
}
@@ -1162,13 +1636,12 @@ import_key (void *cls,
}
fd = open (filename,
- O_CLOEXEC);
+ O_RDONLY | O_CLOEXEC);
if (-1 == fd)
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"open",
filename);
- GNUNET_break (0 == close (fd));
return GNUNET_OK;
}
if (0 != fstat (fd,
@@ -1177,6 +1650,7 @@ import_key (void *cls,
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
"stat",
filename);
+ GNUNET_break (0 == close (fd));
return GNUNET_OK;
}
if (! S_ISREG (sbuf.st_mode))
@@ -1256,7 +1730,11 @@ parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
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,
@@ -1266,6 +1744,7 @@ parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
ct,
"DURATION_WITHDRAW");
+ GNUNET_free (secname);
return GNUNET_SYSERR;
}
if (GNUNET_TIME_relative_cmp (overlap_duration,
@@ -1273,9 +1752,10 @@ parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
denom->duration_withdraw))
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "taler-exchange-secmod-rsa",
+ section,
"OVERLAP_DURATION",
"Value given must be smaller than value for DURATION_WITHDRAW!");
+ GNUNET_free (secname);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
@@ -1287,6 +1767,7 @@ parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
ct,
"RSA_KEYSIZE");
+ GNUNET_free (secname);
return GNUNET_SYSERR;
}
if ( (rsa_keysize > 4 * 2048) ||
@@ -1296,8 +1777,10 @@ parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg,
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;
@@ -1341,6 +1824,7 @@ load_denominations (void *cls,
struct LoadContext *ctx = cls;
struct Denomination *denom;
bool wake = true;
+ char *cipher;
if ( (0 != strncasecmp (denomination_alias,
"coin_",
@@ -1349,6 +1833,23 @@ load_denominations (void *cls,
"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,
@@ -1394,28 +1895,36 @@ load_denominations (void *cls,
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,
- "taler-exchange-secmod-rsa",
+ secname,
"OVERLAP_DURATION",
&overlap_duration))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "taler-exchange-secmod-rsa",
+ secname,
"OVERLAP_DURATION");
+ GNUNET_free (secname);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg,
- "taler-exchange-secmod-rsa",
+ secname,
"LOOKAHEAD_SIGN",
&lookahead_sign))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "taler-exchange-secmod-rsa",
+ secname,
"LOOKAHEAD_SIGN");
+ GNUNET_free (secname);
return GNUNET_SYSERR;
}
+ GNUNET_free (secname);
return GNUNET_OK;
}
@@ -1435,6 +1944,8 @@ do_shutdown (void *cls)
GNUNET_SCHEDULER_cancel (keygen_task);
keygen_task = NULL;
}
+ stop_workers ();
+ sem_done (&worker_sem);
}
@@ -1457,6 +1968,7 @@ run (void *cls,
.updater = rsa_update_client_keys,
.init = rsa_client_init
};
+ char *secname;
(void) cls;
(void) args;
@@ -1471,31 +1983,63 @@ run (void *cls,
/* 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,
- "taler-exchange-secmod-rsa",
+ secname,
"KEY_DIR",
&keydir))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "taler-exchange-secmod-rsa",
+ 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;
}
- global_ret = TES_listen_start (cfg,
- "taler-exchange-secmod-rsa",
- &cb);
+ {
+ 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);
@@ -1520,10 +2064,9 @@ run (void *cls,
}
if (NULL == denom_head)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "No denominations configured\n");
- global_ret = EXIT_NOTCONFIGURED;
- GNUNET_SCHEDULER_shutdown ();
+ 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,
@@ -1547,6 +2090,11 @@ 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',
@@ -1554,13 +2102,18 @@ main (int argc,
"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 */
diff --git a/src/util/taler-exchange-secmod-rsa.conf b/src/util/taler-exchange-secmod-rsa.conf
index dfa87f050..978c40258 100644
--- a/src/util/taler-exchange-secmod-rsa.conf
+++ b/src/util/taler-exchange-secmod-rsa.conf
@@ -5,19 +5,22 @@
# 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
+OVERLAP_DURATION = 0 m
# Where do we store the generated private keys.
-KEY_DIR = ${TALER_DATA_HOME}/exchange-secmod-rsa/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
+UNIXPATH = ${TALER_RUNTIME_DIR}exchange-secmod-rsa/server.sock
# Directory for clients.
-CLIENT_DIR = $TALER_RUNTIME_DIR/exchange-secmod-rsa/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
+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
index 61fa58811..ffbceb48e 100644
--- a/src/util/taler-exchange-secmod-rsa.h
+++ b/src/util/taler-exchange-secmod-rsa.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
+ 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
@@ -24,14 +24,17 @@
#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
-#define TALER_HELPER_RSA_SYNCED 9
GNUNET_NETWORK_STRUCT_BEGIN
@@ -73,7 +76,7 @@ struct TALER_CRYPTO_RsaKeyAvailableNotification
/**
* Signature affirming the announcement, of
- * purpose #TALER_SIGNATURE_SM_DENOMINATION_KEY.
+ * purpose #TALER_SIGNATURE_SM_RSA_DENOMINATION_KEY.
*/
struct TALER_SecurityModuleSignatureP secm_sig;
@@ -133,6 +136,28 @@ struct TALER_CRYPTO_SignRequest
/**
+ * 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
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 1af383dcc..57d73b14f 100644
--- a/src/util/test_amount.c
+++ b/src/util/test_amount.c
@@ -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);
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 5ee06487b..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, 2020, 2021 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"
/**
@@ -38,8 +37,21 @@ test_high_level (void)
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
+ };
GNUNET_CRYPTO_eddsa_key_create (&coin_priv.eddsa_priv);
GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
@@ -62,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.
@@ -82,37 +117,88 @@ 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 TALER_BlindedDenominationSignature blind_sig;
struct TALER_FreshCoin coin;
- struct TALER_CoinPubHash c_hash;
+ 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);
+ }
+
+ 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,
- TALER_DENOMINATION_RSA,
+ GNUNET_CRYPTO_BSA_RSA,
1024));
- TALER_planchet_setup_random (&ps);
+ 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));
GNUNET_assert (GNUNET_OK ==
TALER_denom_sign_blinded (&blind_sig,
&dk_priv,
- pd.coin_ev,
- pd.coin_ev_size));
+ false,
+ &pd.blinded_planchet));
+ TALER_planchet_detail_free (&pd);
GNUNET_assert (GNUNET_OK ==
TALER_planchet_to_coin (&dk_pub,
&blind_sig,
- &ps,
+ &bks,
+ &coin_priv,
+ ach,
&c_hash,
+ alg_values,
&coin));
TALER_blinded_denom_sig_free (&blind_sig);
TALER_denom_sig_free (&coin.sig);
@@ -122,6 +208,117 @@ test_planchets (void)
}
+/**
+ * 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)
{
@@ -129,15 +326,24 @@ test_exchange_sigs (void)
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))
{
@@ -147,12 +353,28 @@ test_exchange_sigs (void)
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;
}
@@ -161,7 +383,7 @@ static int
test_merchant_sigs (void)
{
const char *pt = "payto://x-taler-bank/localhost/Account";
- struct TALER_WireSalt salt;
+ struct TALER_WireSaltP salt;
struct TALER_MerchantPrivateKeyP priv;
struct TALER_MerchantPublicKeyP pub;
struct TALER_MerchantSignatureP sig;
@@ -211,20 +433,109 @@ test_merchant_sigs (void)
}
+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;
+}
+
+
int
main (int argc,
const char *const argv[])
{
(void) argc;
(void) argv;
+ GNUNET_log_setup ("test-crypto",
+ "WARNING",
+ NULL);
if (0 != test_high_level ())
return 1;
- if (0 != test_planchets ())
+ if (0 != test_planchets (0))
return 2;
- if (0 != test_exchange_sigs ())
+ if (0 != test_planchets (13))
return 3;
- if (0 != test_merchant_sigs ())
+ 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
index da1c51b46..0119e4278 100644
--- a/src/util/test_helper_eddsa.c
+++ b/src/util/test_helper_eddsa.c
@@ -365,6 +365,7 @@ par_signing (struct GNUNET_CONFIGURATION_Handle *cfg)
int ret;
esh = TALER_CRYPTO_helper_esign_connect (cfg,
+ "taler-exchange",
&key_cb,
NULL);
if (NULL == esh)
@@ -427,6 +428,7 @@ run_test (void)
nanosleep (&req,
NULL);
esh = TALER_CRYPTO_helper_esign_connect (cfg,
+ "taler-exchange",
&key_cb,
NULL);
if (NULL != esh)
diff --git a/src/util/test_helper_rsa.c b/src/util/test_helper_rsa.c
index ac4ae1dc0..2bc15879f 100644
--- a/src/util/test_helper_rsa.c
+++ b/src/util/test_helper_rsa.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2020, 2021 Taler Systems SA
+ (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
@@ -129,7 +129,7 @@ free_keys (void)
* @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 denom_pub the public key itself, NULL if the key was revoked or 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.
@@ -140,7 +140,7 @@ key_cb (void *cls,
struct GNUNET_TIME_Timestamp start_time,
struct GNUNET_TIME_Relative validity_duration,
const struct TALER_RsaPubHashP *h_rsa,
- const struct TALER_DenominationPublicKey *denom_pub,
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
const struct TALER_SecurityModulePublicKeyP *sm_pub,
const struct TALER_SecurityModuleSignatureP *sm_sig)
{
@@ -155,7 +155,7 @@ key_cb (void *cls,
{
bool found = false;
- GNUNET_break (NULL == denom_pub);
+ 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,
@@ -176,7 +176,7 @@ key_cb (void *cls,
return;
}
- GNUNET_break (NULL != denom_pub);
+ GNUNET_break (NULL != bs_pub);
for (unsigned int i = 0; i<MAX_KEYS; i++)
if (! keys[i].valid)
{
@@ -184,8 +184,8 @@ key_cb (void *cls,
keys[i].h_rsa = *h_rsa;
keys[i].start_time = start_time;
keys[i].validity_duration = validity_duration;
- TALER_denom_pub_deep_copy (&keys[i].denom_pub,
- denom_pub);
+ keys[i].denom_pub.bsign_pub_key
+ = GNUNET_CRYPTO_bsign_pub_incref (bs_pub);
num_keys++;
return;
}
@@ -267,32 +267,66 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)
struct TALER_BlindedDenominationSignature ds;
enum TALER_ErrorCode ec;
bool success = false;
- struct TALER_PlanchetSecretsP ps;
- struct TALER_CoinPubHash c_hash;
+ 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));
- TALER_planchet_setup_random (&ps);
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,
- &ps,
+ alg_values,
+ &bks,
+ NULL,
+ &coin_priv,
+ &ach,
&c_hash,
&pd));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Requesting signature over %u bytes with key %s\n",
- (unsigned int) pd.coin_ev_size,
- GNUNET_h2s (&keys[i].h_rsa.hash));
- ds = TALER_CRYPTO_helper_rsa_sign (dh,
- &keys[i].h_rsa,
- pd.coin_ev,
- pd.coin_ev_size,
- &ec);
- GNUNET_free (pd.coin_ev);
+ {
+ 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)
{
@@ -321,7 +355,9 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)
if (GNUNET_OK !=
TALER_denom_sig_unblind (&rs,
&ds,
- &ps.blinding_key,
+ &bks,
+ &c_hash,
+ alg_values,
&keys[i].denom_pub))
{
GNUNET_break (0);
@@ -365,8 +401,10 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)
default:
/* unexpected error */
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected error %d\n",
- ec);
+ "Unexpected error %d at %s:%u\n",
+ ec,
+ __FILE__,
+ __LINE__);
return 7;
}
}
@@ -380,15 +418,18 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)
/* 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));
- ds = TALER_CRYPTO_helper_rsa_sign (dh,
- &rnd,
- "Hello",
- strlen ("Hello"),
- &ec);
+ ec = TALER_CRYPTO_helper_rsa_sign (dh,
+ &rsr,
+ &ds);
if (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN != ec)
{
if (TALER_EC_NONE == ec)
@@ -405,6 +446,227 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)
/**
+ * 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
@@ -417,9 +679,23 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
struct TALER_BlindedDenominationSignature ds;
enum TALER_ErrorCode ec;
struct GNUNET_TIME_Relative duration;
- struct TALER_PlanchetSecretsP ps;
-
- TALER_planchet_setup_random (&ps);
+ 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;)
@@ -428,6 +704,9 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
{
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),
>,
@@ -439,12 +718,16 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
keys[i].validity_duration))
continue;
{
- struct TALER_CoinPubHash c_hash;
+ struct TALER_CoinPubHashP c_hash;
struct TALER_PlanchetDetail pd;
GNUNET_assert (GNUNET_YES ==
TALER_planchet_prepare (&keys[i].denom_pub,
- &ps,
+ alg_values,
+ &bks,
+ NULL,
+ &coin_priv,
+ &ach,
&c_hash,
&pd));
/* use this key as long as it works */
@@ -452,12 +735,19 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
{
struct GNUNET_TIME_Absolute start = GNUNET_TIME_absolute_get ();
struct GNUNET_TIME_Relative delay;
-
- ds = TALER_CRYPTO_helper_rsa_sign (dh,
- &keys[i].h_rsa,
- pd.coin_ev,
- pd.coin_ev_size,
- &ec);
+ 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);
@@ -468,7 +758,7 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
if (NUM_SIGN_PERFS <= j)
break;
}
- GNUNET_free (pd.coin_ev);
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
}
} /* for i */
} /* for j */
@@ -507,6 +797,7 @@ par_signing (struct GNUNET_CONFIGURATION_Handle *cfg)
int ret;
dh = TALER_CRYPTO_helper_rsa_connect (cfg,
+ "taler-exchange",
&key_cb,
NULL);
GNUNET_assert (NULL != dh);
@@ -558,12 +849,14 @@ run_test (void)
return 77;
}
- fprintf (stderr, "Waiting for helper to start ... ");
+ 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)
@@ -594,6 +887,34 @@ run_test (void)
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);
diff --git a/src/util/test_helper_rsa.conf b/src/util/test_helper_rsa.conf
index 6f445fc56..d50e64d95 100644
--- a/src/util/test_helper_rsa.conf
+++ b/src/util/test_helper_rsa.conf
@@ -4,6 +4,7 @@ TALER_TEST_HOME = test_helper_rsa_home/
[coin_1]
DURATION_WITHDRAW = 1 minute
+CIPHER = RSA
RSA_KEYSIZE = 2048
[taler-exchange-secmod-rsa]
diff --git a/src/util/test_payto.c b/src/util/test_payto.c
index 4dc73a964..62ba7d28e 100644
--- a/src/util/test_payto.c
+++ b/src/util/test_payto.c
@@ -22,16 +22,16 @@
#include "taler_util.h"
#define CHECK(a,b) do { \
- 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); \
- } \
+ 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)
@@ -50,11 +50,55 @@ main (int argc,
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);
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 199863448..bf59ba6ec 100644
--- a/src/util/url.c
+++ b/src/util/url.c
@@ -102,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)
{
@@ -212,28 +206,12 @@ 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);
@@ -244,59 +222,49 @@ TALER_url_join (const char *base_url,
"Empty base URL specified\n");
return NULL;
}
- if ('/' != base_url[strlen (base_url) - 1])
+ if ('\0' != path[0])
{
- /* 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 ('/' != 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;
+ }
}
- if ('/' == path[0])
+
{
- /* The path must be relative. */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Path `%s' is not relative\n",
- path);
- return NULL;
+ 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);
}
-
- 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,
@@ -329,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,
@@ -372,7 +325,7 @@ TALER_url_valid_charset (const char *url)
for (unsigned int i = 0; '\0' != url[i]; i++)
{
#define ALLOWED_CHARACTERS \
- "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:;&?-.,=_~%"
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:;&?-.,=_~%+#"
if (NULL == strchr (ALLOWED_CHARACTERS,
(int) url[i]))
return false;
@@ -382,4 +335,20 @@ TALER_url_valid_charset (const char *url)
}
+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 274dad3cd..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,16 +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>
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;
@@ -37,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
index bc4903e0e..0b6ab5432 100644
--- a/src/util/wallet_signatures.c
+++ b/src/util/wallet_signatures.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -17,23 +17,123 @@
* @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_MerchantWireHash *h_wire,
- const struct TALER_PrivateContractHash *h_contract_terms,
- const struct TALER_ExtensionContractHash *h_extensions,
- const struct TALER_DenominationHash *h_denom_pub,
- struct GNUNET_TIME_Timestamp wallet_timestamp,
+ 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,
- struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct GNUNET_TIME_Timestamp refund_deadline,
const struct TALER_CoinSpendPrivateKeyP *coin_priv,
struct TALER_CoinSpendSignatureP *coin_sig)
{
@@ -48,8 +148,12 @@ TALER_wallet_deposit_sign (
.merchant = *merchant_pub
};
- if (NULL != h_extensions)
- dr.h_extensions = *h_extensions;
+ 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,
@@ -64,10 +168,12 @@ enum GNUNET_GenericReturnValue
TALER_wallet_deposit_verify (
const struct TALER_Amount *amount,
const struct TALER_Amount *deposit_fee,
- const struct TALER_MerchantWireHash *h_wire,
- const struct TALER_PrivateContractHash *h_contract_terms,
- const struct TALER_ExtensionContractHash *h_extensions,
- const struct TALER_DenominationHash *h_denom_pub,
+ 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,
@@ -82,11 +188,15 @@ TALER_wallet_deposit_verify (
.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
+ .merchant = *merchant_pub,
};
- if (NULL != h_extensions)
- dr.h_extensions = *h_extensions;
+ 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,
@@ -104,12 +214,48 @@ TALER_wallet_deposit_verify (
}
+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_DenominationHash *h_denom_pub,
+TALER_wallet_link_sign (const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_TransferPublicKeyP *transfer_pub,
- // FIXME: consider passing hash!
- const void *coin_ev,
- size_t coin_ev_size,
+ const struct TALER_BlindedCoinHashP *bch,
const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
struct TALER_CoinSpendSignatureP *coin_sig)
{
@@ -117,12 +263,10 @@ TALER_wallet_link_sign (const struct TALER_DenominationHash *h_denom_pub,
.purpose.size = htonl (sizeof (ldp)),
.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK),
.h_denom_pub = *h_denom_pub,
- .transfer_pub = *transfer_pub
+ .transfer_pub = *transfer_pub,
+ .coin_envelope_hash = *bch
};
- GNUNET_CRYPTO_hash (coin_ev,
- coin_ev_size,
- &ldp.coin_envelope_hash.hash);
GNUNET_CRYPTO_eddsa_sign (&old_coin_priv->eddsa_priv,
&ldp,
&coin_sig->eddsa_signature);
@@ -131,9 +275,9 @@ TALER_wallet_link_sign (const struct TALER_DenominationHash *h_denom_pub,
enum GNUNET_GenericReturnValue
TALER_wallet_link_verify (
- const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
const struct TALER_TransferPublicKeyP *transfer_pub,
- const struct TALER_BlindedCoinHash *h_coin_ev,
+ const struct TALER_BlindedCoinHashP *h_coin_ev,
const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
const struct TALER_CoinSpendSignatureP *coin_sig)
{
@@ -142,7 +286,7 @@ TALER_wallet_link_verify (
.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK),
.h_denom_pub = *h_denom_pub,
.transfer_pub = *transfer_pub,
- .coin_envelope_hash = *h_coin_ev
+ .coin_envelope_hash = *h_coin_ev,
};
return
@@ -153,10 +297,40 @@ TALER_wallet_link_verify (
}
+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_DenominationHash *h_denom_pub,
- const union TALER_DenominationBlindingKeyP *coin_bks,
+ 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)
{
@@ -176,14 +350,14 @@ TALER_wallet_recoup_verify (
void
TALER_wallet_recoup_sign (
- const struct TALER_DenominationHash *h_denom_pub,
- const union TALER_DenominationBlindingKeyP *coin_bks,
+ 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 (struct TALER_RecoupRequestPS)),
+ .purpose.size = htonl (sizeof (pr)),
.h_denom_pub = *h_denom_pub,
.coin_blind = *coin_bks
};
@@ -196,8 +370,8 @@ TALER_wallet_recoup_sign (
enum GNUNET_GenericReturnValue
TALER_wallet_recoup_refresh_verify (
- const struct TALER_DenominationHash *h_denom_pub,
- const union TALER_DenominationBlindingKeyP *coin_bks,
+ 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)
{
@@ -217,8 +391,8 @@ TALER_wallet_recoup_refresh_verify (
void
TALER_wallet_recoup_refresh_sign (
- const struct TALER_DenominationHash *h_denom_pub,
- const union TALER_DenominationBlindingKeyP *coin_bks,
+ 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)
{
@@ -235,12 +409,68 @@ TALER_wallet_recoup_refresh_sign (
}
+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_DenominationHash *h_denom_pub,
+ 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)
{
@@ -248,9 +478,14 @@ TALER_wallet_melt_sign (
.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
.purpose.size = htonl (sizeof (melt)),
.rc = *rc,
- .h_denom_pub = *h_denom_pub
+ .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,
@@ -266,7 +501,8 @@ 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_DenominationHash *h_denom_pub,
+ 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)
{
@@ -274,9 +510,13 @@ TALER_wallet_melt_verify (
.purpose.size = htonl (sizeof (melt)),
.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
.rc = *rc,
- .h_denom_pub = *h_denom_pub
+ .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,
@@ -289,4 +529,1302 @@ TALER_wallet_melt_verify (
}
+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 */