diff options
Diffstat (limited to 'src/testing')
105 files changed, 21143 insertions, 0 deletions
diff --git a/src/testing/.gitignore b/src/testing/.gitignore new file mode 100644 index 00000000..17a848cc --- /dev/null +++ b/src/testing/.gitignore @@ -0,0 +1,8 @@ +test_auditor_api_version +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 diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am new file mode 100644 index 00000000..fd5fb944 --- /dev/null +++ b/src/testing/Makefile.am @@ -0,0 +1,308 @@ +# 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 + + +# Libraries + +lib_LTLIBRARIES = \ +  libtalertesting.la + +libtalertesting_la_LDFLAGS = \ +  -version-info 0:0:0 \ +  -no-undefined +libtalertesting_la_SOURCES = \ +  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_auditor_exec_wire_auditor.c \ +  testing_api_cmd_bank_admin_add_incoming.c \ +  testing_api_cmd_bank_check.c \ +  testing_api_cmd_bank_admin_check.c \ +  testing_api_cmd_bank_check_empty.c \ +  testing_api_cmd_bank_history_credit.c \ +  testing_api_cmd_bank_history_debit.c \ +  testing_api_cmd_bank_transfer.c \ +  testing_api_cmd_batch.c \ +  testing_api_cmd_check_keys.c \ +  testing_api_cmd_deposit.c \ +  testing_api_cmd_exec_aggregator.c \ +  testing_api_cmd_exec_wirewatch.c \ +  testing_api_cmd_exec_keyup.c \ +  testing_api_cmd_exec_auditor-sign.c \ +  testing_api_cmd_recoup.c \ +  testing_api_cmd_refund.c \ +  testing_api_cmd_refresh.c \ +  testing_api_cmd_serialize_keys.c \ +  testing_api_cmd_signal.c \ +  testing_api_cmd_sleep.c \ +  testing_api_cmd_status.c \ +  testing_api_cmd_track.c \ +  testing_api_cmd_wait.c \ +  testing_api_cmd_wire.c \ +  testing_api_cmd_withdraw.c \ +  testing_api_cmd_insert_deposit.c \ +  testing_api_helpers_auditor.c \ +  testing_api_helpers_bank.c \ +  testing_api_helpers_exchange.c \ +  testing_api_loop.c \ +  testing_api_traits.c \ +  testing_api_trait_amount.c \ +  testing_api_trait_blinding_key.c \ +  testing_api_trait_cmd.c \ +  testing_api_trait_coin_priv.c \ +  testing_api_trait_contract.c \ +  testing_api_trait_denom_pub.c \ +  testing_api_trait_denom_sig.c \ +  testing_api_trait_exchange_pub.c \ +  testing_api_trait_exchange_sig.c \ +  testing_api_trait_fresh_coin.c \ +  testing_api_trait_json.c \ +  testing_api_trait_merchant_key.c \ +  testing_api_trait_number.c \ +  testing_api_trait_process.c \ +  testing_api_trait_reserve_pub.c \ +  testing_api_trait_reserve_priv.c \ +  testing_api_trait_string.c \ +  testing_api_trait_time.c \ +  testing_api_trait_wtid.c +libtalertesting_la_LIBADD = \ +  $(top_builddir)/src/lib/libtalerexchange.la \ +  $(top_builddir)/src/json/libtalerjson.la \ +  $(top_builddir)/src/util/libtalerutil.la \ +  $(top_builddir)/src/bank-lib/libtalerbank.la \ +  $(top_builddir)/src/bank-lib/libtalerfakebank.la \ +  -lgnunetcurl \ +  -lgnunetjson \ +  -lgnunetutil \ +  -ljansson \ +  $(XLIB) + + +# Test cases + +AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; + +check_PROGRAMS = \ +  test_auditor_api \ +  test_auditor_api_version \ +  test_bank_api_with_fakebank \ +  test_bank_api_with_pybank \ +  test_exchange_api \ +  test_exchange_api_keys_cherry_picking \ +  test_exchange_api_revocation \ +  test_exchange_api_overlapping_keys_bug \ +  test_taler_exchange_aggregator-postgres \ +  test_taler_exchange_wirewatch-postgres +if HAVE_TWISTER +  check_PROGRAMS += \ +    test_exchange_api_twisted \ +    test_bank_api_with_fakebank_twisted \ +    test_bank_api_with_pybank_twisted +endif + +TESTS = \ +  $(check_PROGRAMS) + +test_auditor_api_SOURCES = \ +  test_auditor_api.c +test_auditor_api_LDADD = \ +  libtalerauditor.la \ +  libtalertesting.la \ +  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 + +test_auditor_api_version_SOURCES = \ +  test_auditor_api_version.c +test_auditor_api_version_LDADD = \ +  libtalerauditor.la \ +  $(LIBGCRYPT_LIBS) \ +  $(top_builddir)/src/util/libtalerutil.la \ +  -lgnunetcurl \ +  -lgnunetutil \ +  -ljansson + +test_bank_api_with_fakebank_SOURCES = \ +  test_bank_api.c +test_bank_api_with_fakebank_LDADD = \ +  $(top_builddir)/src/lib/libtalertesting.la \ +  -ltalerexchange \ +  -lgnunetutil \ +  $(top_builddir)/src/bank-lib/libtalerbank.la + +test_bank_api_with_pybank_SOURCES = \ +  test_bank_api.c +test_bank_api_with_pybank_LDADD = \ +  libtalertesting.la \ +  libtalerexchange.la \ +  -lgnunetutil \ +  $(top_builddir)/src/bank-lib/libtalerbank.la + +test_exchange_api_SOURCES = \ +  test_exchange_api.c +test_exchange_api_LDADD = \ +  libtalertesting.la \ +  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 + +test_exchange_api_revocation_SOURCES = \ +  test_exchange_api_revocation.c +test_exchange_api_revocation_LDADD = \ +  libtalertesting.la \ +  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 + +test_exchange_api_keys_cherry_picking_SOURCES = \ +  test_exchange_api_keys_cherry_picking.c +test_exchange_api_keys_cherry_picking_LDADD = \ +  libtalertesting.la \ +  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 + +test_exchange_api_overlapping_keys_bug_SOURCES = \ +  test_exchange_api_overlapping_keys_bug.c +test_exchange_api_overlapping_keys_bug_LDADD = \ +  libtalertesting.la \ +  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 + +test_taler_exchange_aggregator_postgres_SOURCES = \ +  test_taler_exchange_aggregator.c +test_taler_exchange_aggregator_postgres_LDADD = \ +  $(LIBGCRYPT_LIBS) \ +  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ +  $(top_builddir)/src/bank-lib/libtalerfakebank.la \ +  $(top_builddir)/src/json/libtalerjson.la \ +  $(top_builddir)/src/util/libtalerutil.la \ +  $(top_builddir)/src/lib/libtalertesting.la \ +  -lmicrohttpd \ +  -lgnunetutil \ +  -lgnunetjson \ +  -ljansson \ +  -lpthread + +test_taler_exchange_wirewatch_postgres_SOURCES = \ +  test_taler_exchange_wirewatch.c +test_taler_exchange_wirewatch_postgres_LDADD = \ +  $(LIBGCRYPT_LIBS) \ +  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ +  $(top_builddir)/src/bank-lib/libtalerfakebank.la \ +  $(top_builddir)/src/json/libtalerjson.la \ +  $(top_builddir)/src/util/libtalerutil.la \ +  $(top_builddir)/src/lib/libtalertesting.la \ +  -lmicrohttpd \ +  -lgnunetutil \ +  -lgnunetjson \ +  -lgnunetpq \ +  -ljansson \ +  -lpthread + +test_exchange_api_twisted_SOURCES = \ +  test_exchange_api_twisted.c +test_exchange_api_twisted_LDADD = \ +  $(LIBGCRYPT_LIBS) \ +  libtalertesting.la \ +  libtalerexchange.la \ +  $(top_builddir)/src/bank-lib/libtalerfakebank.la \ +  $(top_builddir)/src/bank-lib/libtalerbank.la \ +  $(top_builddir)/src/json/libtalerjson.la \ +  $(top_builddir)/src/util/libtalerutil.la \ +  -ltalertwistertesting \ +  -lgnunetjson \ +  -lgnunetcurl \ +  -lgnunetutil \ +  -ljansson + +test_bank_api_with_fakebank_twisted_SOURCES = \ +  test_bank_api_twisted.c +test_bank_api_with_fakebank_twisted_LDADD = \ +  $(top_builddir)/src/lib/libtalertesting.la \ +  $(top_builddir)/src/bank-lib/libtalerbank.la \ +  $(top_builddir)/src/bank-lib/libtalerfakebank.la \ +  $(top_builddir)/src/lib/libtalerexchange.la \ +  $(top_builddir)/src/json/libtalerjson.la \ +  -ltalertwistertesting \ +  -lgnunetjson \ +  -lgnunetcurl \ +  -lgnunetutil \ +  -ljansson + +test_bank_api_with_pybank_twisted_SOURCES = \ +  test_bank_api_twisted.c +test_bank_api_with_pybank_twisted_LDADD = \ +  $(top_builddir)/src/lib/libtalertesting.la \ +  $(top_builddir)/src/bank-lib/libtalerbank.la \ +  $(top_builddir)/src/bank-lib/libtalerfakebank.la \ +  $(top_builddir)/src/lib/libtalerexchange.la \ +  $(top_builddir)/src/json/libtalerjson.la \ +  -ltalertwistertesting \ +  -lgnunetjson \ +  -lgnunetcurl \ +  -lgnunetutil \ +  -ljansson + + +# Distribution + +EXTRA_DIST = \ +  test_bank_api.conf \ +  test_bank_api_bank_twisted.conf \ +  test_auditor_api.conf \ +  test_auditor_api_expire_reserve_now.conf \ +  test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv \ +  test_exchange_api_home/.config/taler/account-2.json \ +  test_exchange_api_keys_cherry_picking_home/.config/taler/x-taler-bank.json \ +  test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/wirefees/x-taler-bank.fee \ +  test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/offline-keys/master.priv \ +  test_exchange_api_home/.config/taler/test.json \ +  test_exchange_api_home/.config/taler/sepa.json \ +  test_exchange_api.conf \ +  test_exchange_api_twisted.conf \ +  test_exchange_api_keys_cherry_picking.conf \ +  test_exchange_api_keys_cherry_picking_extended.conf \ +  test_exchange_api_keys_cherry_picking_extended_2.conf \ +  test_exchange_api_expire_reserve_now.conf \ +  test-taler-exchange-aggregator-postgres.conf \ +  test-taler-exchange-wirewatch-postgres.conf diff --git a/src/testing/afl-generate.sh b/src/testing/afl-generate.sh new file mode 100644 index 00000000..b0afcab3 --- /dev/null +++ b/src/testing/afl-generate.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# +# This file is part of TALER +# Copyright (C) 2015 GNUnet e.V. +# +#  TALER is free software; you can redistribute it and/or modify it under the +#  terms of the GNU Affero General Public License as published by the Free Software +#  Foundation; either version 3, or (at your option) any later version. +# +#  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +#  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details. +# +#  You should have received a copy of the GNU Affero General Public License along with +#  TALER; see the file COPYING.  If not, If not, see <http://www.gnu.org/licenses/> +# +# +# This will generate testcases in a directory 'afl-tests', which can then +# be moved into src/exchange/afl-tests/ to be run during exchange-testing. +# +# This script uses American Fuzzy Loop (AFL) to fuzz the exchange to +# automatically create tests with good coverage.  You must install +# AFL and set AFL_HOME to the directory where AFL is installed +# before running.  Also, a directory "baseline/" should exist with +# templates for inputs for AFL to fuzz.  These can be generated +# by running wireshark on loopback while running 'make check' in +# this directory.  Save each HTTP request to a new file. +# +# Note that you want to switch 'TESTRUN = NO' and pre-init the +# database before running this, otherwise it will be awfully slow. +# +# Must be run from this directory. +# +$AFL_HOME/afl-fuzz -i baseline/ -m 250 -o afl-tests/ -f /tmp/afl-input taler-exchange-httpd -i -f /tmp/afl-input -d test-exchange-home/ -C diff --git a/src/testing/baseline/admin_add_incoming.req b/src/testing/baseline/admin_add_incoming.req new file mode 100644 index 00000000..677678b5 --- /dev/null +++ b/src/testing/baseline/admin_add_incoming.req @@ -0,0 +1,7 @@ +POST /admin/add/incoming HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 220 + +{"reserve_pub":"TMZCK5CFM1KZQGY1WTF0CEZZPGA0670G94969RF79PA5106ARTK0","amount":{"currency":"EUR","value":5,"fraction":10000},"execution_date":"/Date(1442821651)/","wire":{"type":"TEST","bank":"source bank","account":42}}
\ No newline at end of file diff --git a/src/testing/baseline/deposit.req b/src/testing/baseline/deposit.req new file mode 100644 index 00000000..a400796f --- /dev/null +++ b/src/testing/baseline/deposit.req @@ -0,0 +1,8 @@ +POST /deposit HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 1658 +Expect: 100-continue + +{"ub_sig":"51SPJSSDESGPR80A40M74WV140520818ECG26E9M8S0M6CSH6X334GSN8RW30D9G8MT46CA660W34GSG6MT4AD9K8GT3ECSH6MVK0E2374V38H1M8MR4CDJ66MWK4E1S6MR3GCT28CV32H1Q8N23GCHG70S36C1K8MS3GCSN8RV36D9S710KGD9K6GWKEGJ28GRM4CJ56X1K6DJ18D2KGHA46D13GDA66GVK4GHJ8N13AE9J8RVK6GT184S48E1K6X336G9Q8N142CJ4692M6EA16GRKJD9N6523ADA36X13GG9G70TK6DHN68R36CT18GR4CDSJ6CW3GCT364W46CSR8RV42GJ474SMADSH851K4H9Q8GS42CHS8RV3GCSJ64V46DSN8RSM6HHN6N246D9S6934AH9P6X23JGSH652K0DJ5612KJGA26N242CH35452081918G2J2G0","timestamp":"/Date(1442821652)/","f":{"currency":"EUR","value":5,"fraction":0},"wire":{"type":"TEST","bank":"dest bank","account":42},"coin_pub":"JXWK4NS0H2W4V4BETQ90CCEDADP6QQ3MV3YZ7RV2KXEM8PWXE8Q0","H_wire":"YQED9FDYPKK2QQYB3FS19Y15ZMKBAXJP2C73CXASAF1KM6ZYY723TEJ3HBR6D864A7X5W58G92QJ0A9PFMZNB81ZP9NJAQQCCABM4RG","h_contract_terms":"1CMEEFQ5S4QJGGAMVYFV07XQRHQA311CR2MTRNC5M9KZV6ETDV1SY00WJFEV2CG9BXQTEQPZAF8A54C2HX32TZCN20VBGPFPS2Z16B0","merchant_pub":"C36TEXQXFW00170C2EJ66ZR0000CX9VPZNZG00109NX020000000","denom_pub":"51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GT58S2K2HJ16H336C9N8CVK4E9N6H1MADHH61330HHM6N1K8E1H8RVKJH256D1M6E1K8RWKJGSH8S2M6DJ170TK2H266GTK8DSS64RKJDJ26D144DJ474SK0GHQ711MAD9G752M2CJ58S1KJDA570SK2E9G8N23GCJ28S146DHH610K2H1Q8CW3GGA16S146H9G68TKACSQ6914CE1H691K2E9N6RWM8H9P8CWM2H9S8GSK0H9P6D1K6H9G6X0M4C2171144HJ46N334H9J692M4H9M8MR4CCJ46GRKEGA46533CDJ38MV4CH9K892MAH1P8S2K6D9K6N246E256H244G9Q6D346GJ56S23JGHJ690KADHJ8H242H2575132CSM6X1M4G9N6RR48E9H8MVM8E9354520818CMG26C1H60R30C935452081918G2J2G0","refund_deadline":"/Date(0)/","coin_sig":"X16E0DP8C2BJNVNX09G24FFC5GA4W7RN2YXZP9WJTAN9BY6B4GMA39QNYR51XNNEZ3H1J7TP0K9G55JZ8V7WS7CZMD7E64HWYBFWM00"} diff --git a/src/testing/baseline/keys.req b/src/testing/baseline/keys.req new file mode 100644 index 00000000..a9503a86 --- /dev/null +++ b/src/testing/baseline/keys.req @@ -0,0 +1,7 @@ +GET /keys HTTP/1.1
 +User-Agent: Wget/1.16.3 (linux-gnu)
 +Accept: */*
 +Accept-Encoding: identity
 +Host: 127.0.0.1:8081
 +Connection: Keep-Alive
 +
 diff --git a/src/testing/baseline/refresh_link.req b/src/testing/baseline/refresh_link.req new file mode 100644 index 00000000..acf3dff5 --- /dev/null +++ b/src/testing/baseline/refresh_link.req @@ -0,0 +1,5 @@ +GET /refresh/link?coin_pub=WQHES0X5XK43VBG1Y8FXR2YEJM04HQVMDTCS07MH691XWADG8QCG HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json + diff --git a/src/testing/baseline/refresh_melt.req b/src/testing/baseline/refresh_melt.req new file mode 100644 index 00000000..98b5b638 --- /dev/null +++ b/src/testing/baseline/refresh_melt.req @@ -0,0 +1,8 @@ +POST /refresh/melt HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 34136 +Expect: 100-continue + +{"new_denoms":["51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSN8CV38D9G8MT46CSG650M4GHQ6N0M6HHR6X142EA26H13JG9N6RV3AD1P6GV34HHK8S0M6CT28RTMAH9R8H0K2GHN68W46G9K8RT32CT560RKCC268RRKJCSJ70S30CA564SKAD9J84VM8DSG611MAG9R6H2MAG9M8MT4CDHG8CWM8HHH84T3AD1N6X0KEH9P8RRMCCSQ8MT30CSK6MVK0GA48CW30D9J6WTK0CSN650MAD1R70TM4CHG850K2G9H89142DT488S42DT36RVM6DSS6GTK2CSJ84WMCDHP8CV3GDSP6GTM4G9H6N246GHH6GWKGD1N70R34HA689148GHP8RVK2E9M8MRKJCJ28D0KEGT26CS3EH256RVKEDSJ74WKGGT16RSM6H9M6GVKGDS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSN8CV38D9G8MT46CSG650M4GHQ6N0M6HHR6X142EA26H13JG9N6RV3AD1P6GV34HHK8S0M6CT28RTMAH9R8H0K2GHN68W46G9K8RT32CT560RKCC268RRKJCSJ70S30CA564SKAD9J84VM8DSG611MAG9R6H2MAG9M8MT4CDHG8CWM8HHH84T3AD1N6X0KEH9P8RRMCCSQ8MT30CSK6MVK0GA48CW30D9J6WTK0CSN650MAD1R70TM4CHG850K2G9H89142DT488S42DT36RVM6DSS6GTK2CSJ84WMCDHP8CV3GDSP6GTM4G9H6N246GHH6GWKGD1N70R34HA689148GHP8RVK2E9M8MRKJCJ28D0KEGT26CS3EH256RVKEDSJ74WKGGT16RSM6H9M6GVKGDS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSN8CV38D9G8MT46CSG650M4GHQ6N0M6HHR6X142EA26H13JG9N6RV3AD1P6GV34HHK8S0M6CT28RTMAH9R8H0K2GHN68W46G9K8RT32CT560RKCC268RRKJCSJ70S30CA564SKAD9J84VM8DSG611MAG9R6H2MAG9M8MT4CDHG8CWM8HHH84T3AD1N6X0KEH9P8RRMCCSQ8MT30CSK6MVK0GA48CW30D9J6WTK0CSN650MAD1R70TM4CHG850K2G9H89142DT488S42DT36RVM6DSS6GTK2CSJ84WMCDHP8CV3GDSP6GTM4G9H6N246GHH6GWKGD1N70R34HA689148GHP8RVK2E9M8MRKJCJ28D0KEGT26CS3EH256RVKEDSJ74WKGGT16RSM6H9M6GVKGDS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GSP8MSM2E1J70T36G9Q750K8D9P692KCHA36MSM4GHS6MWK6DJ564S32DHQ8MT3EHHG84W32G9M88RKGG9J74W3GC1R8H1M6DHQ891K8C9M84TM4CSQ6124CC1G60T30H9Q6CVK2DSH74T4CGT1751MAC1P88RK2GSR8GTKJEA46N0M2DT48CV4CDSP6MSKJE9K64SM6CSS6GVMCD9G6CT3EDT6652K2GJ46RT36GHJ85138H1Q6114AC1Q6H23ACHS8RR46H9M60VKECT688VKGC9S8CW44E1G612K6H1K70VKADSN68T34D1K70R32G9R6934AE246GRKCCJ66N142G9K8CVK8GHS6WS3GH9J74S4ADHM8MSKGD248D330CHK6MVM2DA48GSKCGHH88TKCC9354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0","51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30H1J65244GA27533AC248MW34C1P6N136GSR88V3AC1H8N1K8CJ2891M8DA2712K8E1N6GW48DSJ8GW4CDSJ6RT48DJ28GW46DT46H136E216D14AE9Q60VKECT26S2M8EA38MSK2D9S6MTM4CHQ88T3GD9M8D34ACHJ6MR38CT46X1M6CSH74SKCG9Q712K4GHG712M2D9Q8D1K2E9K6X2K0D1H8RVK0E9Q610M2E1K612K4E9M6MTMCH9P60WMCCHR6CWKGE2168SK4GJ26WV3GGT188S38H1P8MW44D1K6S344HHP74W4CG9M8H1K0C1M8933ECA16D13AH9P6RTK4GSM6CRK0GA36RV4ADSM7524CH9G8N0KAEA288WK0GJ16RWK0CSK6N0KJGT674T3GCS354520818CMG26C1H60R30C935452081918G2J2G0"],"transfer_pubs":[["J65E5480S6ZVPBA5HV9D4APYXFS527DAJGN5HKZ5EWP89EFSKGK0"],["TYZZG5HSXTYJMA9XD3EMTX2S36V7F4VPR1RHQPKJSTWXJD2KPDAG"],["2MN2X2X7P6GJCN09Z6ZDF2R9W1W3BSYJER7FTBSDDSWMRJ7DG0DG"]],"melt_coins":[{"value_with_fee":{"currency":"EUR","value":4,"fraction":0},"coin_pub":"WQHES0X5XK43VBG1Y8FXR2YEJM04HQVMDTCS07MH691XWADG8QCG","denom_pub":"51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GT58S2K2HJ16H336C9N8CVK4E9N6H1MADHH61330HHM6N1K8E1H8RVKJH256D1M6E1K8RWKJGSH8S2M6DJ170TK2H266GTK8DSS64RKJDJ26D144DJ474SK0GHQ711MAD9G752M2CJ58S1KJDA570SK2E9G8N23GCJ28S146DHH610K2H1Q8CW3GGA16S146H9G68TKACSQ6914CE1H691K2E9N6RWM8H9P8CWM2H9S8GSK0H9P6D1K6H9G6X0M4C2171144HJ46N334H9J692M4H9M8MR4CCJ46GRKEGA46533CDJ38MV4CH9K892MAH1P8S2K6D9K6N246E256H244G9Q6D346GJ56S23JGHJ690KADHJ8H242H2575132CSM6X1M4G9N6RR48E9H8MVM8E9354520818CMG26C1H60R30C935452081918G2J2G0","denom_sig":"51SPJSSDESGPR80A40M74WV140520818ECG26DJ384R4CCHP8RTK6DJ46X344C9M6S1KEDSQ8923EE9P84SKAH9R84SMAGT670SM4GT66123EDJ46RVK0E1N65336H226H2K8CA38CVKCH9N711K2H1S6X134GHJ6924CGJ68RRMAH9P6MT30H23651KCD1N6X0M8GA58CS34E9K70V32E1S8MTK8GJ270WK0HA368V3GDHM68RK2CHS610MAH1H8RR42CT58H342DSG8D1MCC1J6N0KEHHS6WVM2GHS60WM8G9G6GTMAC258D338HHP8MWM2GSS8MW3GCA270TK6G9G6WRM2GA56RSMCC9R88T3JEA270SK0HJ284R3CC9Q6WVK0DT570TKJE9P68SK0CSG88T3CCHJ60R48C9N611KAD9N6WVMAC9N70T46GH35452081918G2J2G0","confirm_sig":"ZKYCWKVTVQCWMWTAQRNRSKGQP6AC14VF7W525ZQYFZ6J2T1GM7PE7945W3HNW32S0MZBX8K65VQPXH83NMQR2SGTY7DX2T60RKHFP00"}],"secret_encs":[["DCZPFTHF1GZRXFVSYJ4AK1HH4QE46T8F60X1AQ6T6BF1WHBNTJ3FEGH7Y1TH5A6YT10GFYGV12S8V9MB4HEKHRYBBMR7JJ9KB264TNR"],["ANBQS74BACGAWEC0F0654HYGZB3NBSNXVD4KZY4TRP4JMRADJE2Y5BYV513Y37PPMR6V5P200FEAHTZ5TS3R9G9MBYM6Z144N853PQG"],["1X6P2JR301MPR5JYMKPV1HXAFXKH9M09HWVQ4PS6JNK1K6E8ZSMW1C6NS79CPFP443QBVEW9SRWXXJQT91EHFKJ8007AB4S64SPWN3G"]],"coin_evs":[["1XB4A97X8ATPH1KG5NAMJGJY258AGK0MKVJRHN4NGKPRD17DQM0CMTT351FB7T56NTVA9E5VTH9Q12A10C3YVDEYJB4B66EC1NPXNPMNSVTBJ7HGM4DXFB03K1BXK247QK581CGE54286BTK6B5VW0FTS4AZ3NQP43V9E6VV1HTKG1CM5WQJEY14TAC8P2YSV2XH61RG4E84R","JVNGX2AR684RF4VRYFSS69WNMJBDDS9Y4AXFRX5JRFES5QBTMM51SB84BN61S5W0R7PZGK09NQ8QMX7Z90AE0JJH76J7DCM94DKWMV88K966NEA2CNMS50PG6D0K73619K67S9HJ0PQTQH8YD91P4CQQZTRPV7Y2RHEGMBB2G67ZBVVH1S9Q0HYX69Y2B3J2WYFMH3BJESVGE","6MZ6ZDF26MSAZN3VK3QDY62MD3AXZTK8K46A7ZFNBVCR33AE6H3A309F822EKPHT2YTJB6PMSPSJ3ZAE99Z5Q2J8F87X5N723J713A0QR87KYJ2NZ3PFCS5CC8FXJYK6YXZ4ND6FHNJRJZZZZRXE05VP14XW1BZFYZGQQZ8J05FYKY8AYFNCZ50WT7W8M83XS50GTYJGQTNPT","Q0JWR4WRPYT7TCNCV47RJ1FW3HEFQABN9EE27DBMDB4FYAVK8X9KM38HH9KN8QW28RXFXAKT3MGHXPGRWCY9P313CPR5Z2XSR3ZGKFHQHTP1A02SSD0DHH12RCV5TJ7M86WXWT7F00CQ8HV5H90MFEDX6J2X9JNX26NC0PRH0YXHGW7XVWXF00ZEJCGJDEZ1EVXA5BNW3SAEY","B20PSGCM77EVSXEC6T41N1T55KX76B5K3X38VBQ8NSAN2BKNH0W4DQGVZQTBA2588YCRW1TN9G721GP7ZCP9H32BBC9HM3XFTNJ4QBV2FMM7KSG6T0H418EBNBGKZXMTD9ZDVV6A2TEBMXSEKYVK9HCZT8EEQ7E44DWBDHN274N9D4ZDEFCZXNF0MKBM3TYZGHJ5T71T88CE4","F95WT8Y2WRXGK07MY3FMV1Y6PS44CMP59KK8G82EKW3Z0Y6C6QZ2CVC4A6DF9N3E6HW8SQ3CF4FSD1QC2DN5E9DM5G762DW491BAE5RC2A3YPGJTZ3XBFJ3R3NSRDRZMHR6Y755QDRZZEVFTEPN7MZ90SE6KTQS80CHNMTX15NMZPNFJSCQSEJ9RQJKZBH9ZZFMNXT8BXM7K4","1B8GKDJYJYCZ8Y2HCETV6EWA8FRVR0AAD7TRXFXBHNF7G10CZYDFTKBJ8X0J0M7VR1KQV67SF5VMM7VVG5ZQ4F9QEKRV3GSBB4M75CAXSEXSHDT36RJ6A98EKP3EZXT65HX2K788ZE4SVE5N2NJ8Q26P318PK9X69YWB4W7R1F1F9WHZ8WKJ69G5YZ1T9WR1N8CXTJASXW3A8","FV0Y6B21F01D5EH31R80BHG7GKA08C7HN3FYDMGQE6HJ2Y9ZSVJ17HR8Y22WGPN98T08E6XP4PZNMJ3RHAFK8DAVW3AYM5VNPHV3JSB5B64F0H60EMQNA59A2QAFRV3FCHJQATY87SGT9E67HRVE5QDP9HJC7A351WM55VV5MW3T251VVZ8N5V2GVX6MD14MR9ZX9TBS4N30J","K8QSPQTEV9YZC7MC6AT9R8Y0QGG4PT1V45KSSZR2YY9SBA3QEQG7R6YR4RKEKR5DGDM2K4DN9K128TQMMPFWZR8N95F29Q8E2WBD22F5SRFPJ2KJA6SEZ6RMPCHG6C4BJEV7PZAEVTNJDK8T79PJCB5B97D5WQ52TT2DB6Q4S599D61AA54FCVM9R3AB03PDTYZ0PCKBMJ23G","1655VA0GT3ASG2JVEZ93BWE01YDEA70ZW2FD1W3AZTD4B4WK9GYPMM9Y6KXDGDDB2TMDF5CWXNAX0B7E396E1NH3GCK8KFHMGXNPG0CTD2KNW9X3QB4HKK68JM2WGMF05VJ6K4G76R4A10JWPEZTD554TSFYVEM0355C9E5P9W5GFFFYQMTAXG43V8GP7P4BDBD81Q9G87MXG","J3T1MSDB9ZHCCNAYDXEQAMETNSCX5DJYQBCXGREMYAWAQ8AJCY5JV00HHX31DVT7X5S25CEX88707E81X4KNCN7RBRFMFNP8QZHFZHXNS6MN7BD4MGRVQEC50EWH6N6EFY1GA039KYJKX24XMGZXKB27H5R0RKN7YC00EKN99SGKF3YN7DPR8880MG457438G1YJR7BGRCG7J","DX73NQ14FVC978B1HP03MWBD83YT6NVP607WB126RAJPKPB11GSWB7H9W0QJSH9WD4SN9JJE6FDMQZTMJXCN1AKDBKKVPWW9VBEAE0YVCG64S16TXMSF5FTWNXYG7RDTHK03BXDGVXSPZFE7A2F6FPK9WCBBZSMKB8EJFGJK1PKKBHVA1Q2MX91NK32XPF00WFWZCC3N72JY2","PKSMQXMCT9H53FHGKVVPPTTHJ0M5X4SSR0XARN34VV7DX9ZA9N8379NVQKN4E5V6JXAPEVGPREWD720RZCVB90DDHRFW8GG6G434GR1A77SVPG9GKP2DEKBVP3CZ9WDMQD9RFZZ2Z3ZQ34YVD2J1Y4AZ52ADY5FXPB06W9NQ9NH7AQVHTPMSTP181MZM507M777SAPSCR9J8M","1XV578XYBQYYSV9V8ZRACRG3JTN103BZFPF5JXHF8YDCCCJM9XS343JYM8N6Q3J8D0NF2T8TPZKCB32MC1GXWZZRRWQW77JSSYXGA8136DNFYTCKB8EP277KBXAYXBW0ZX7S13J5NDGR7ZJ7QTAAPTEAG9MX6QBT9Z7ZZ1MECV7Z0D7GE7Z6KWASE5T4SW0MFHKVD1EAFNG0P","86PDN3KMT8AW8TVXEBGG3199YSEDMQX91QW82QVZ9TPYCNS3YKK3NCK5J64VKPKGJGQJP8E3RX97NZ4RDQ0PR21QS93MTWXSHKCTK2KTH7TQPKVQSWPGVC8CZDEGD9X484HJKMQG56RP1MZE7AWKK9C5ZK7MSN21GTG1KNF591Q8DVX0TM06BD8VGF6QNC745CFRMRSYHXK7P","KGXXZAECQ6DETR97HRYKFC4A1WAKC3S0FYNHS16J342WMPPP78NHWZXSEPN04XEQY6QNR9ECWZNXY6BZHXWAW2EV42295GBVA4R2J2KAK9EF7MR2789EHY0MVQJME1ADGEPGCQ02WHWT3C173SPDKW1DA5C8HBF847KV00A3F3ZM2HE18N5MFFQXBB014HK0MC4NW6G19AESR","92G786WTFN4BP3Z2RX1Q79ZPHGMPYC21K646XA8AB97V103DSJY0S4Y1FHHSHYQ8WZPWF0W07VY6CDG66438K8E8DWN7CCSB6R8KSC2VNAQBVC1ZD2H9VG33309B6QEKK5PYAGJZN5NG0CJXW9RFGPCNJJZW6KHM4TCMDJVKV9P953Q0TWVM2YZWK37504QJ41MZ4KXN52M30"],["NG0RYVSNPXP4B2NEYCTVC5A0EW5PFFW8V3PH9X5FFQ2VHCETMWDPZ75HBSC8QDBCWZ6EV9SEN1SMEMXABK3YTXBDDXYTN0JNKX1ME9T71JJTZZQ7XA1890DZR3ZAMD0XW8GZENDNAP4R5B80DQYFDEENSB141DZ0HG3A3FZA7JMCSBY0Y0PB2AXNS25Q6CQ5WA7GWCWA09PD2","R70NVRHFT6HCP69K8QQM9HQX0CZQYG09VV2VA7RKCVFDRTNH44XZHRBEJTBW1ASDS7WYA47PBVPB9T77B32ER6V2745Y8B1P22CZ5T70WTE6Y4HSS60CM5SFB6QT1VXTVW9G0DAC3XY2QDX6TKKQA1HX3RB0GPJH47D08KB1Y4EP1SXW851CFNEE373YJHBNYXMSE3MJBCQFP","JGZK4NS2E8D1CTR8ZAR5CHGN7T09Q41P545ZNNB7RMYV0CZ9CQWJWZZWRQZDRDCHV5PQS9G6WZB37CHQMZ7Y207D23FQAPMN6CP30CXYEQ449R3V7B8BYGJ84VYW60201B4343GG75TTTN947VVEYA3BWKRY4M5XCXSPAGW1TTAH9VAE8CBXBKAKJESX8XWS4W5PYBG8793TR","CEVHPNNSWR438ZWEK27YRYBKZXVBT8AM9QP2KXWC59ZSM8Q7J0GZNXZYBES04RB7BJY4D5YCPT3VRT7EPK0576809CG98X5SPTQPSR4TZ0R6DWMXH9893FCTVJX2A4H2EGSGP3Q6H53FD4CSDBMZFZVACXPMW1CR07A1GG7XFXSK1BP1BTGMBQD0818G6BC6SBFTXS1Z68PD8","72BFMSPJ57Q1B88JS6NX8YETQP0GSSRHXM25ZBGNW06YGYJ7FT1D7KVGF4PRKKQK8KN4MGHK5DXRQDTNEJSRZYGAN6DV5QAW9PACT7FQQXG6G4JS7J129VXFQP3DYTV0455Q37H5YPVGMWXZ4GNBQJ86HWWD670XY8JAR4WTYHX8FWJXZW57Q94KK79SD51HBJH1SJ4Q5C04W","2MDED6PYFV264D0SETN9MD4F0SADGVD75J8Y16DTSABNPW09AKE7A5F4QNB2124WDQ0HW4SGHCX26NAG46Q960Y01VYSX0GQHVKESSMEAZ2426GJ8KR0E6KTAXDZY6HZ6DRSPXXGZ07YZT2XF696CP8215HE92RGT7A50H5XXV2FDGFWJXYNCB7QND27ZWYWTK0KTCRYT104Y","267QZPCB0QGC63V9E75Y41J5285Q3FX6KQHM08MAKZ248X9WJK5H6H903C0T02W4G3EA22E0C1MHCWP5VRAEC8D0J0VYW9JNCH9YGPQJ3P941AADABVXG4M2K724C8VCFZQNXYJAMY4Q65WYHW6VYPN9H1K7ZABPAZX82SSSBPYKJZBREJ15MSS50R9HJ6FWW7T9P8ES015PY","7W8HPWAGBVFK0KTWG02RRTWKAE4W0PJH76GH9JWD7H48BS7CVD1T38QBX7HJVC6PJEAJ5NGP7MSJ9D5H1FD2T9Q5X4YQMQ1F80H4G8GRGXFKS863G48HN15D72EZSM9V8M75FXCH3639PHENNV35GA6TWPRS70HEJSBAX9A52DWTB63ZCSZXFGACZEQFGR0EKEE0AZB3HKE2J","JMGGS2DPDKNQKTM593M69Z1R331654KR6N12Q7EZPX20BMHV1P9JZZ8HNN6VHQ76YKWNMW9HR3173C2J66R8EVVWZAR001Z18F9HJ9YBVSM1Y94PJSM57GNVRNN0GH11A3ZRCA4WBKFC64A4RE6KTYMMJKJNHTR9A75QC7JWG5YY9D0J8TM9KXQW8495V702VS0E40121HNHY","REH6B17HSBJGMMPYKG7EA1J4G1D7H95N20P647AWHJKY3VA5BDZKN84FEG0G0FK6TKNN8D0TDFQ41610SK9CBR27SFD16PKEZ7AP97NB9ENPQRQ9PEKR76KB6VFWP666MJBTQJ5R7WGKTTE3ZN5Z2VWY2CPB437746CYBN52G2P2Q7V6SDGXV5HPYJBKXESS1JMVH3EHDC988","MRE9Q877Z18VMFNH1PWNH9N3Z5YE2SDZCVVFBZAA41SSRC2F81D6F9TCZP2AGYAEXWM3977KWJ6SV215ZJ24RAWA94NCKEFQ1CMW8MYJ063EP68NVVPH51SFVYQVHYC45MGNEXK61R9KFK6RWYV5Y6CHMAYZTW5M17WBSHBZ79F4DSWHVVY7774FX3WEEVM8EZ0WMJWY99A1P","2CM59NJVJ68NYX5ZJM2ZX7JK8EW7JXAVE0H2D1ZTX9X6X72P9768WK5GG3ED7MGEYGFQBK5YSSJ0DM68DRPDPTAHQGWTGJ7E79GAW96BXHYV64BQXXEV9YQ0XR84G8VZWQ8Z7T9VX7VQ0HHX1SAS3NGNZAH1H03KPFNAZMNMGV3H4Q3PC96JHGAP5JNDWA1FAFJ410REB33D0","2KEXQVJHACQX7V1XGF8PA6DV0EVAKFNSA037YJ3BP91NNQB60ZBAEFT9DKVW2M19R51B8HGQ2RCBSWBRHD0RFC95TYGJVKNJSCMQPXV7SM6A1JH18DFPWB1AJBS4SSVJXZ9V72TY2P8Q8ZTBMGCYERXCT4QJMASTQM1T22C9AXMMPZ1BHKNB2FBJ3TY9AQP4ZGHRSRBKR75E4","5Q9ZFEFF6C6XPGM78EXAWJF2CKJVJ0SPA113XT7R5JTRW7MKE70TETKK1MBRSTF566AEZB2CBX6CR10J6DPAQ92GC6XYK27A52KQGQHR5W346RGF28WE2PPC8S3XWTGCGVP318AKKTC8GZQ62FRYZVPTVF07HWYHDESQFKYNSDRNV8Q5KPGRQM27SK3CX14RA2K9KZ0NJJ0FE","7ZK7H90WBS080M9TH8XH9DNENKQ78YHYSXWF16VRM76YDKP4RG97DEM1DBX84VXH98M64CJS1GYTDKG4XA8XCNSZ4KQ12GAXCJC168DBR8Q4M29X2Q70WNR7BZDNH0DQJMR154M0C1NH2EHJBKA76PNZT2369PT9Q4961EBCY5FE85EKCX7584GXBMHQF1GRDVCCDVY7TH210","CFSSPFE27VKZHPS7D48AAHE0C2XKXS774CDPFE530P5C4EFZVZEP1PTS3NYB7KBNHNAGMZX8YZJVTNJAB1DE7GCRG43Q2JVS15RGJC4K9PDA0Q2MG4DGRX1CM28HE8CB1XWVW7MCDJHPNE28NWN0CYHDES8GHAZ6Y50NNZRN31FV8C28V0PRNZK4WFAPJ4EGV5NQPHGSZAWNG","9XJB29QY06J1DYBXQRV9F7M3T11JTGSZHX8D0Y80Q07M646VTHNFKNA19CG84BA2DKRE1D5DS3M72QH29S9EZEADW6PCS1FXGCMGZ0RQXT5ASSX7VBQ40NGPYY6PFE6CYV45YYEGPGJ2DCQCNYP2Q9E78SBD53QTQVYZG7W3QE8EASEPSZXP1VVB1TYYHN0B1BZGY2JKGWWPY"],["CSATAT8AQRB91XKHJD1B7MK1DPVPV991WZM4Y2NF0G8SNQSR391KBDGHR0AWGZ8V6EYXKNGTACT9GRBWWAPYTDPWFZ5QPY9GY8Z4YDXRK1QW30YT6JMAYQM2TH36XP81G2X55785ZJWCRXZ9QQDGGMMM8QZTWN9TPW8JXH4XM8A0DPG5354PF3782GVC8J8DXD7KY88KT0FZY","4MG8W9X1M5361HRRG6W9QF13EQDRFWVAQGMWJKEESZ3FBCWP7QQGQBBDT3ZYE3XARBVGJAVFQRQPAYA1PC4CQYRX0CZTS0E4QZB3ACMXG9XJ91VPP7X0ZG08WXWMT02M9V4HCHJ7MJQZ7EWCRJAG7E14BQ465R123P6QFGY770GN0BBNT040S3XPC88CD8X3D8497DE2YA1MT","DCSBAHV955VGACX5WE2N02QZSPQ9N375DCS0W3298N8KF59YHVTS7FYJ1NE011VWF8ET7F6SQZHW1NWJZW3XYWN2ZMWXCRGH80CD6N8YA6EE8Z2N2PW4T65M0DDX4YFXZPG61E7JJGK03VTDW5J8WS2F2R88GNNJMWBQCYBWWTZ34H7J5XAPBBY3AF4M6A8C0RAHPX0FS044W","5CKW4V17Q2XAV54F07X0C2NZCRZ21B9M79JM37F10DZJ3YMY9WPEWSYEDC6DP1QZ2DRRAXDCFG1WZH3C0Z58Y26XGSC28R9Q3ZFSKZHS0F0DC08JWY01W9GPECCN0CP0Q19N7GWX7HAFVR0PR89P0DTBPJ02NKCG14HZK933C6C62AFS8GFWNWN7FJ042YVSH69BAM9FQX4JT","G3J5ZS034N9TKESCEBQJQVXJMYB7HX48QT7QTEZA688019KSSCFVDG2TD963JA9NQF2G80Q3J1853PRB8QD2WFWDSMPP9X4FXKAEH79Q2X8K2KZJ120848KY421H81WAA1AMPZ6APD67RDRBKM0H5GN974TBQ8S5ENFRJSNTJRJEDY57GRHAN8V2R2PCNCY1VWH92EN1MJA4P","BFTC88T9TMD6EQPMBN6M7ZES14AGZMK8CYF0BQ7JAPPAPKXRTW83Q2ZF5270PG9WN0SET02R2HHFEM4J66GDWK2GYCF6GBZWF0GAH9ZC0AHVT3JW2XWDZ2X1DH2WB9Y10A7QB83S3E1S5DQEJXD6AS4J6HCBJBZJPC1EJZP3E5R5ANY1EYZ7P1692WSNABA0KWZS5T05DK51M","HFBFB0NVHM82461FBYT7SKWR1R38QD6MPMTSZXQAAH7CKVSKE21M4KZMWE3EF2C0FFJ6QGZPFWYXM58NANQMHP293WNJD5NYM31ZGV3ZS8SDP9DK1ZVSF5WXW3PEKYCAR3C7J6QZKAT1CVQF86P6M3S7XPVN8EV212SG4ENANH650EF7H5RDBN29PRREWB5JA4PSX9TQR1Y92","1JT0M6407RNBHQFZ0SKEPHQW2ZTZTQ5X7BZ1Q1FBQVR9QBH3EP20CH1KJSP71PC8Z4WFKJ97442BWG991RJC0XE7Y71PW9SXMPX4MG1SVN371ZG3GNCBX3Z9AZAAR9PGHS46AVG1RH71X1WQZ7W9HGQPRYDPCMQBQ7N2PD0AH8EJMT2ASXKPNK09S9H9B0ZTYMB5KK2VB7C72","E5PWK8V063470P271354CCZ3JS5H6GB8GWPDCDB125CVT4YHR3MGTYAFTDJP38F2WSNERY5KNE9VK85N70WC6VZ22WF7MABK2FJZM3179NHBV92FQC19BR21002AZHHE4A171QYFX69NXV3XA90HY27ECQ28YCPZNKEPQXXVCNHVSFJJE8553M68KM81EW45GXEV3797W2KX4","JQGN8RXXYMHG8MD2GSNQ6NQ8QCH8A6WDT0S0WY4ZP5424RGY975300CQC1C575K619FY2JN71RQ62RM6W2TBVZX0Z5B8A2PQ2MTN6W486AHBJKX6ST9HA05EQ9VMJN3PGDSF1GACENFK8CA1EW4KNG185PG6CP7YY1TNX3FZ43H020K8SYTQWMPM6FAZ5N4MA6CQND1ZJ6AC0","NJ5PM2Z4VNYKG6X93KWH0GQZNMBKHYM2FWX0BSNPGJ18Q7R32YC9FPC2Z45MPJ5EZY4E3FD0CD67TB9K4879SQ33SCG4Z9BARNQVWXMK975GRGAB4306D1W7PF1QD1MTVW79DHY3M2VVC01R4T41M3TXYTD1X3JF1PBGC4T63MF9MJS4KZTS03Z84AY6C9WK9RTNHXN7R0VJC","1ERPPXVG2NJ4RHCD8QED1E25R6QCAKKZWRV3EAVS4Y17ZDTXK46TSBJ1MF4HN26K3FHRE3PVBV4DA1RJ36R70BSAHSW7KTEGJNR8M5FBJ7ZDT907X2H0RMPCDANGGT9Q8VWYKBAHQ1T72S0ZAEC079SKV16XWTRX698P3P2TCJQDT7EA0G67N82YFPRE7YVGFKTMYXYC7EH1E","91CHR983Z222JN1695J11YY836RHP6J02SSZ8T5ACZ15X0D1YAW8VTCMECW3CPMCHG2RWTS5C30HSRWTYHAE3J3F61Q5N34ZSBVFPWJMN8WSFTAFEG7WZDWCM1VJZX5KZGXF7RTCS09MHJ0WRMCGC18WSR2SDV64P9HE1WTNCD8KT65JGQ3ZPWAWVJNJS63YNTGDPZ4RHTZ78","8Y2GMEETX3N9XTAB124JYKMHQH29009WJ5KF6V31WWZ99GXWZJHC5C03P3CKMB4R8M7ARQ3XD7AHPJTY2EKKPPF9QXWN7685HDTPT2SBCQ67XHHRA2RJ5E1S4VFP2YB1H0W1D9RE4C6WDT7FNVWXFHXAEQAJRJ577QNJD8G4ZSARSJ2SP6625AW9VQVVRFS0FWMBWJZNHXGWE","33MH8RQ5PXQFB0Q2T0H30VM9G4J0ZQHB3JHH8MWF5K3F3KDBAR20ZG4APHCTF1XRG530W7K2476P7BNFKENP2WXJ4HAW6RS91EQKGYD6PEHRYCVF8JJTMWR32WFVPV5Z2M1FWKNNC5N4CC3MK7S2K1V05PQMZ347KYD45FW7Y4KKJJ89TDPT7SFSQP1A0J00SN3MMTSEGXMVC","HR05CWNNQ5VBAF0WNHP8DZGN3S0DGYJ37EYP8S57MK8KDXQHXTPV2SXNJ2M5SZMS43BR168FJTB1SC9EW9P9YT7DYRBRM7G0VSD86P4QBG42NH06XYCCF7TVN9H9EB5K1G07M2Y3TT4WBAZ2Z350PV67ZANG43054GZA4N08BN2WEDFN94BNFKF45C10C3FFG77D9P3PDMGVG","JS43E6R3YEVTRZGRTS3RF0F9PCW7NBTWB6XEV146AXNTSKEKCDQ8CX76GX6HA06N0RTA699M99FXMD4JX04VY961YXJ18G96Z1AEEJ5BR5A8GYEWSNJSR5Y55ETBCKHBHR6CJ8A3659Y45FNG7H4C1K44WZ520PE4NAQVJ9QS1H2RWQ5ZZ8H8J8WGBDQC6DQYRTQHR0WF2156"]],"link_encs":[["7A1HVQDZ7C2M4K913020KMYGK4K86PM30APZ8AH5ERT5Z83QZPJRTGEFXF0TGYH36R1N36N3VKB5V845FK33ZW3R4G01X6CZNE37XHXE707T0TA4DYGYNX295QSVF5VEPD8QMK1J7ZAGDS27QP9H1QVK3NRBTZVRZ23XAV642CGEFS1ZMPWEDSHWECQ4G6CFK1V0K2118BQYS41R9P05NZ18PY3Z1FWNZH472HTKWT74KC752S0W1C2ASM428009","VN0WT37PVBAEYW3GH2ZGZ6X37R0XATMMGHJ86G953NVD6TE608Z3A6P9PG9XAFAGPQ13GSQ435C8763NR5T28RQHH4JB9N578TW7FHA5Z4Z6MDS6CMFK9A6ZGGT35G28NH6YG55EZR6GCM5E2ZKA8A9TR8Z8C6N4BTEVJCCJRWQ5MG8W4TR5N3YNPA7NAM49TYECT56BHJPCD6DHJ3XQF2XTZQTCECDJWZG4TXK3DEZM7Y55XFJ49C7F1P17MWQJ","88RWAYX9765MBR4KBM2BN9NSQ5KHWFC0AQZ6B0RF617VF5CSJJZBB69XEVDFC12K5D0T06KBE71BHK9E5ZTEWD5FK4Z2JVKD6WV6QBFHQ8Y1ZPP7Y2EYB9XFW7S9NVM27FNX8RKWTAJ45FFYKJPNR7MX5HZZ77B25ZB57Q0NJB1E7ECH4QP5BKZG63CF5M75VA633AJC7A4XWR8JJTTQDZ40KJMXE15X9KPABVZM6HSGF19P3N2SE2VSSWFXF3XH","MBPTJC2E7C7YQMAKCJN8EYBX1Z0CG8Z35G5VW3AZH3PAM6DF32AXKFX06Y9686R1CC1TP5BWX94DMN78RXSZGXFTEKPK3RSEVWQZCBR6BWB7YEEJZTV0C5MGREJE73RP40730074SPPW6CW665XV5TJA5329ZM0XC0KJ3MR9V70TDCE3M9DMB1JXPN3M97A74XG6KNJ57FKDQH4C4NV3Z3MJ276Y9MDAPDF7GM7MW7AQ7T1Y98RJ4H6163WCSN6V","Q2YNH71E69EDZ1NF8PFRS66PJYZ360SPQVQK73422944P34FGDZVJPMF59Y5KVJSB8VTJRPVMBJQX5Q8M8BHEJX1YAHDXAAWBTTBFM12CACZT72CYH6S80TJ8GYGS9ED1HBBWWT8RTZGMD8R4NMBJJRED107EASZJTH4SW32FJ04X62EA7YAMAJFTPCC5E6QJ43F0H592Y3J81NXQS66P6GQXKQMCN5W3FX8ZJ3160V542YVE2FNV3X7WD88FVX3","5H1650VJX95VFVA0G8ZNGWBCACCZ53D6SNJ2S4QVD27X2128KWC4MXRH8B3FK89QXV07NTEAJEXGDZC8BSYW11JZGF7H3D2YVXX8ZEEZBHWESCN7TNSE9AQK5NBV709PNHP9KHE0K7KEJTY2GY08CWAN1G9CQ3ZPF71NEHC7C0C47GYNVHX9PTXAYH2ZKQJSRNNSERC62CFX7NQD5TPF6WFEYC9R1AB3TWEQQF3FKY5EAE04EAEQJWSW0ZFGRCZZ","F5DZP7M59MGX6YJ3M3CEXVSJ0FGD3Q3EVHSQDSDDKSFPJ7ZBN60SYEBB293PDCFZVKC7K4HZ54E3XM7G3GECK2ENZYRCQMS6PS7VM7DYKF7S56KP0N4SE6X69Z6EDA309XPHHPDGRG1593SZ63YN63M815C2JP9M5E7H4FWYWQQNA9C81FQFNH48NVF43G3A54EEM3JV6VKB7X9H3SESC7YRA862NZF42C2FP5MCSACPQBSX0HV2Y6QRZW53BTW2","HJ9AV53HDRMNPEJJPSEPJV98XXVK1D93WT9CQNNY7B0GKY2TTAW55C83EHJQ6SHMRYEWQPBS096ZKRDFHNM5YJF09V0NFEYTTJGSMFCDYKW5GYCZRH3FGZHKEMNN3S98WV5KWDG5PT0S9TCYTDW074WPNMTH9V9KE08E6N8ATBX62PZ2X326TT3N15RG3ZKHQ09360981ANE6MVMTM2J4RXPFMA936KTXAS0EF4FTTGZA1GHS4FNG0JRFBXTNF0D","BXGHTGZ5EWJY0XKATMGECZB8QM74WH1RY4FB4KZP0VQ4STWVBY0NMQ8832YWB04GQ2B50Q5RTD61091HH1R3S2DK6QD7VJYE9BZ4EEQJTDTSSQT86DQEK2QPTXQ8W39WBTAT8PKBT037JM9DCYAC4CRPGTQWBZK3M1Q8BZHRVXYVGGVC5GWG3YQX075EQCSD9Y18N08JKNFHJKVZH6X27ABDHC323HQS30VS15BD65GZ35MH4HBFT7CAHPDZ5C4D","D3EF8ZKVNK6PCTC7KWGDGTSA3804J3N76R186NKQC3YJPW9P9B6CXDN9910S5RNEGST7MSY566PFZY2NP95WZNJTMG8988H3HFVXTXK2TST59AFBNDNTGAK3Y149WVWNDAVZ3G4NM9R75HSWC75BGNDRJ1F6DQ8QACZRAN9E4K6J3NZ3BNG5A2JY48TVXEC3E6TEDMF3E4Q9QSWQXPEHE48TJB5J7Y98WTDDWWV3S186BGM5V06PTGS3V2QRMRW1","YXS4AQF8X7VR35BBA8RBRMCJCSRJ1K2820ERJV75TZ5X3XBB4S5A1EKAGQHRF6KENMX4GEM3NT30K20FD9W03B79AKARV0KK6SMW0W1DY68KWGF69JH95RCHXCSVVV8XGVC48CBW1ZBVPFXS1BEJRDBHJV5229R5CZ9AXC3DNSS7S2QYENQZT7ZSKM9VKYGN604GT55SXD0NXJ0QCVT6TG8MTGTTKSM9D1DFKGDRMP251FCEZVG4XGZSVHKGVHGY","2QYSG97X93Z5TR1JT9FJJG05T1ZKZCPKZ4FNTBNF3DT2WPJR12QF1EPB5NSYQK068XFRKK46YGTT5GX6QEJJZ2B61A35RM30HNTP9S794V2TSME26S4RG32M6AYCNZFR6YEXBKF828ER3HMS1XAF8H475HQSM4M0004GN2S2EHMBXR62TYBEYE20GB123X5V9B31W9650S1E833Y6AH8SKQRE5JY1AK0NATPP6DBKWRC3FTEV6QBDVDGB8B3FKVB","XHTD1E2BZMY311GRBP3PJ6TNJ5S9MMWYZEPFKBF98X6Z3B9GK8EKYZ9DMN0CR9JYQ95KVWETVEKGWMF3V9VT3KK29WGDBPCX1V44J09E2AA1V72A6Y6B6KXPPESB3SM2TH0DZZC1AYQG7TNAMWTQ028ECEZAW30DE3S4ZWKE78ZZJVKP4ZXFXWN3JTRY2TX112B3TRYH94GNYJ1JKGHVG55ESYQTFN7VPPW8GMRSJRVNJZ09S4P37G21HBH46NCK","2N7GXE2T60XCGFPMTS8YJZ64G6STTNPQ4R31CCRJ9Q3F7KJK9RNW4JAQ0VP0A0SCQGR12HJ09NW0TMWKFP46XHQWRJP9WMQA8TXPP6F1SR9XQX7A8KB73EGH3BPN8X5N7YY95M7ZBD1F4SJK24HR0P0GV6BMEEQFTE86QGTP67Z7KC1JPANYM1B3MX9Z9CVESD83WX4VG459X6YR2HR8T576M0EV5K73P9397H25ZSP0NXSAMGY87R0DCQM1GFR3","7WKT07H36FAWM8W3X54AN2VAGJC5DWKP2MJMHES1NA2VFVJD3WAWJJCNWARWP17SQSEWS38HF36A63BNR5Q0Z2R7VETVASYVEYRFQYFGW3KXCVT5AS2ZHJCDMWRJ0N5EQXP0J7MNMCCCQZ8F304SKWWMAQJ8EB5Z7KKM1T0Z8WQHZACR8YTATF4XSXSSJVPGGCXNC47B11QM4RN3HDGXF9MBZZMKASBZRF8AA2EH4M1VFG8NR243ZE3ZKK3818C7","FKZQB344EWN58WPNVZ0BR5A7D30ZR58HZN0PQPVJBKWK3F4A4DPNEV3Q8QXR70E8V5WT4JX8AG9H3X5RZ74DQPKH7RT4M4747NY98P4J09R58JP7BR58KA8Q0AHS6069GHNWC0SX9GF33RV9SA5ZYA75CV37V1N5GRAPX6A0SWR76B6HYSPA94G5PY02R9B05W8Y5MDWDEY70NW4ZXWG2ZH3S68SSNP6SASAJV85KMH6CH7FZXTDYQTAR0HDB4K7","3073VTHEC9679DTAPZZ2J52N4PXFJ2B5JDQQ4FEPWCDFWMTXJK4KTSVMSF6DNMY8BH3EJDS192CK16JZG95JGDGYXMYBAFCY4X77AGA33DZSNXT32VWNVKKP6PN5E7JWRDJT8SSCRJ6C90P9YZ31SFF4PPKFPSVZFRC3PD87CKT8M9D6XDWN61D0PMXKMWGJVSNQDR8NP86MJYE8KGHSX0SWDV2J9N3X9212CXV1JDQNS3DFJ7XEX38EEKWQDSJP"],["8HRTEYDE0W9T678MM8X20QADH4XHBW7JTHJVFDJDANF90K84P7FK7TN0KJGE5BW52VQQFB10YNEQ8S9J3WVRV229RSMC9FKNRJ60968J536EYP24C5WNACGATG6AG8N4YNDQNQQM8V2KQ6DX2XVEH3Z86W9ZYKW7SS4V4B2WM1ZT9WA80SM1H73H9JN62GMMQE37XHVQZ212836WQJMFFKR29ZSYZAE45FJ14AQMP6YKF56M4F1PSATNFZ62VC87","R2CW58HWX89YHVRJ8WYRJ1A2GSC24H1BQW38VGBXP0MXYZS939BWPG8KQM4ED9M19QVXKZ4RC21RN62EC7E1SSAF5VW0BFBGER1FWW3ZHJ78FFF1TNJGRVBE2PF18W1ZXMATHZ12RRKDPSYT81YTGRGAYZ71GHYD1SKYD711P2J80ZTKC6YCWJ6172Z14VWVFA6H8JWWA8VYZ4XTARPXZRWR1Y1YE4232XYZ8X6A8HHWQXS5ZNSM0WPK8BW0KSTA","DE3DY2F0TCKEXVV65EHTWFKKAB2ANTDS435K869BXNVA8NQH4JA6X3WM0H5VAVFT3RCAVQ1WYCRWZ2PKDABAFEXF8RP1R4ABX04ZZS4XPB9K4R1T5KK2HX7GMWQX8RZ16Q40C44DJ70YTHVBD0BM6AF2R5G86SRZ8KERPS21RMPYWYBBWM8DR5YS1D3HM6CEJE6ZX6K398XJGK59E3FCHYJX0B2SCRKJB5E5G1S6PAB3QXKZ2SDGGQR7F8XSESZR","ZNXFM7CDW0M2XRNBTD1GM4FDG74G2XDMTF53N7R71J93YPMPXTT4G6CVZM9YZHCK6GQ35P9CHEKNWK9Q3VESZCYR7TDMMQYYFBNPPM8E4CVHNHFZSBSAHP6MMSWQJ07GZA9FRW7ZGDH6ZNEWNVMQK0V8GV0035C8PP2SYMH66FWMA24E3D15RMJSF4C817TD7SD4F7WG6RCBQ6Y0DDHAY4VW1TB5V4W55J3N0GEMX7AJ98T0TSZQRS6R9PSP1JA2","2J8G5KH2TYWDYHA2KX2CEPW58CRDHK05YX5TER84CGAEBAQASD9Q0ED8R67VBVMDQE2C9NG7V4T3QZ2VRPJPYGQXFPCXBQF99FXK1GBMEGCVP8Y4S4TTN8NJCRBTY6CXA03KQ00JKVJMVSWA85969C2F8YAG8GS5TBPF3D8ZT2C56D2ZD65ZB7F0E11S7B2RWWES7TXQ7HKXDWEBFMYTC2WNGZT1NET0JG21MP03KPS6BEMAX5Q5B139QJNCW8F2","K1605K54DR7EXXPBSWWFNJMXQE6QZS3BN3WWVBF2Y83WD42NMB9FZJG0HXPXVGVJTV1EFEXBAV7PY28FAXTVJHAZ06CDD13ZF7JE12D1BN6T6AT4RHN6N00A2ZJQTE1VCWGBF49Y5B75ZXNS7F9K352AR5A5ZXG21KV7VNT2ZPJ2B1YQG11QY05W920TEGSC8TW0J41HN035ZHCG8KBF5QN5RMGKSVZQV708K7DPVYM7NC2V7X2ZHZ44CBCPEXHD","MGQFD9DMYZD61RHS6KFGC5YGRG2Q56CYNX4GYBGASQ1P69N76M6P4CA3WZV163VFR71JPAXPK11BFCVVCS3A9E59WAR6R0ZWXXN5P9XJXD7DSA1PWCS0JVJ851BCMPWE54M23JGHNRJ03VE76HW8ZH45B6WGG809T8T2HQRBHBPFN3WC9NSE7NY07YWH6V3MFMVPQ2GZJ6QJP4C9D2AGM88H71PD0H65YZY07HD72F3VV1QBJX6T1GPY3NYPEHH3","KWJQX6FGX9XTEXEXYVWJ657TGZHYWHX24KEE1XA5TW5NVVQMQGQVC6HE6GA4RBC1MM0S1GWN7Q838QQEP5DQNDZS7VDBSEP7SPHC832WRN492FN8CEVSJ01AAE36HRXNM8DN6JPK160DJ4M842W9STXQRWPPF94S6HER2WZX330EZ9CECD2NKWTPTYSDGA5FVZS2GC9ERQGYP4YKZWZ8DXRJWCNVE1G7BYYPWNTHPVX0EW5F9GHT4MR29YG9YMRS","50FNFP9CQNHME6EDC0JFNXDVHW58CMC0TJRRZ0FGAYK0MAYQ9EV5WZEYBDV5Y967H72AQBJ28GXD394BDW949KGEMNSAWYE3YX5GSZZNZ9CCXEDKJJSC7W1P1Z04B9VAAM07SPWN4EMREMR9R4XXCD014YHPMMKP64C58X93FCSFJNW8XDF5FW0S2KZJHSP8FGRNQ6D62CQJCVPS615D4N52XNPWTFS1R379GVKSZ4FRR6SRTT4R3PA271HTX3ZQ","NA6QQDXM6SB52138M8CN3E4ZY7P0P9QS9AJ6EGNKYYK58XTRR4D4BANF0ARSTVZGMSJ7FP803B87J21SR2KT35V5K4FEHWVQ9WB9F9ZFMREFAA3SP3NWXNYJQQS0YA2K3RRAWJYJ2XSPQERXW71702QAEQRZQPMGXCMDKFNY9V7Q77147RQ7M87G8ZHZHX83D9C6QHKWGKTSQ0R66MZRCX29C6P6A5EGHACWVXD32Z8SA1ZPNJBDZY0PNJJM68D0","4M00E1KQVQ5KTJPXTCT3EYFN1SH975ZASNAWN7Y0NB12ANR6VZ1163GKCK9WBQJ34Q9P0G763PQ0B68WCJT2KXYXX5X3AV91BPDGGWBYB220HQ52V5VES6H7D75KYS176MA27G09X9PAGTQJ78VQHDAZCYZ5TS82D7HM5GT618605NVEYTYQYT3BA38R6V0RHYGE0CHNEXZTM0TFGDXQZ3P6CTNMYPPPM4E4PMCCQC4XR2S6CGVWYE1TXN0DC59S","N98TSJ6HGBS2N3K3MRPW6WEHBP409B4DC7PW8GMHM2QM2VGC0XQE1AQQ9AT1J4MZAK0B3RAMWF74XZT0D1CBDFABDY8M2M3X6QM7M4C8Z2DV7B2KHF3GBV4FMEG2PWAPW97QJ47383BT5XEYC4KQNXNK1K22NXYQJK0W5GS64KRFZWWKENZABG8VYZ7AYDAXMPVRSCQCCDE1RS6V8MDAVP65SAB59NSM0QQMBAPVBDTWD0P3HCZMG4F17QGTYZYP","ABT20WXHNAHYANMGCVGR9VC1QEQMS5YYRMMPS8GE04RK7XDF4CFXV9HEC1TAN77PBZJXAGX8GVZ8Y0068T84M1DQ90F2VEZGHR1JBBJ91S6RER8FKC301STT9MSF0CYBQTG5GAZK948305FBT6QRFX5BYS4NB5ZX86YMTNZ01V5C26W6Q6T78E5MHC7PH21SQ2VERSMDBWC640068HDX4PCJWBV7809DBSHR2ASJBMM32C49SG4EYVW74H4385QA","M0HBDKZV4TAYEBJJFBMMV55735T3F114RQMHBW2FRTDNNEM6NVBYR65Q25GJ1XNZJV0J7CY55CWMNEARHS2F9TYZPM0BVFZ429DVB2BTX8ZJ6Y9M1RSMVZZZM3GVE865PFEYCVG40AVC5T01Q8XGW1FDZKZTXBVCR5J7H24870S3BJB820X7MQMXDHNNT7VA4XZQJRN96JNHFC3RWB8R2X6Z4BZA9AYETNHP180MY0CJB8CJ8P9DTFHFRAY4M8WE","HV2FVXRE3NGW0WR4XMX0QZX2Z0B7MQB2ANCC95GY0FY9WD74D6CSM11YC0HNMBYCX8CTX7QJS28FGK9FC2RP79M8MKBGHHP0E3SK33AHRQ6457TSMARYKRY2SNSTV7JC8Z1KPV55EH5EH9C736G42J9W84E61JRWG8PMD24V1TS0MJZWKNP1QKSET7N40VAJXZE6B2QDPH2D7F26S4SND52B8KS1HB0NH762F41QE22ANVSXV6S2TN73Y5MXTB9S","70M04PP2WVXGK3NJCMPXGQQCCCDTRW0AF5F8N8A605DHY4CKBFFXX9F5Y512JS3CY6MD9T7K1X0ANT3N5VJRWPF9Y1S97EA2NAZ2W783DEP32Q1M3Y93VBHKCBQNM9M456VSMZA16E8SP3EGPJ9K7QVE0KYV0VWFJRXCA2CWDNNFDEKEJ6GK164SKGZ5MV1QQEDM258SKTQQ0EER895QRAA50PHNNQF1Z8MGHMZCV9WP0ZSJASVK63E4Z073TTVD","799TV24HVN9JDQBG0WFGA5FH0RNJYFJG2XF1B93W5W571CRPMEAHCAKA294YT63VV0K7C46AQYF1AVS58ZJ58CESDYWMY770P95EXPM5H38NV3F3RDTDB7EX5Q9XN0X8QRW37BTN8VBQ4PZ6Z1Y4539C7QGE2F012RBKGK47BST1TQP3XA427H1CDGT8HJBHBY8BD4TH1N7EE0XZF2QV6AJ0JCPWD0H6TKPVMGH1QYKVEKEPQBX20S8K502AYGKQ"],["23NQ8CW42D7FAY7FD7636SFA7THRQA2EGJYMFYF75FA1ZATVSMPPYSHTEC58J84PKDHRD0KPFMK811CTSGW0W274D8A3E5FKEX6P5AZ2ST0K9PK65ZP6R6CR8RMWSGQQX5VAJ2YVJ3RNV20C7YJA0CDCJNQX3JKGRX5NRY8GY3RNA38HG3SM0NW85AKZ0SXP1XRZRY0A0T3SM6E4Q6PTJMJZJ677BRZHXAGB7BEMSK6KDERXHVN9AGN4J9SZ5AY0","ZNZFHJ4X5T79G4DXBX8TPG0RV8SV4B7ZKV56CAN893MZ6Z00CNFPH6KPPXTKM8MB6MTPSCHEA0PRGRHSAKPBSAVR019GB9E75TFSXV452ZQN8Q54ZH9BJPKMAMB26X8BDQKGYK01FAWRWZXJ1TW9GVPJ9TS03WKQS64TVRNH17TTFVED3K8MFMDC9SFSJ4FZC2J19XTSWASXSY4BDE4T8Z2VGWEGDTYF0PNPMHDN2FQN2T25T6TA2DW702HM9MBC","SCC6Y84J99YQ598PR67G5VDT1N9E6S2VEZ22QWD7PQDX108ZZJ2AK16YF9PS8WSAP1PCPM0AYD1R9522XZNGWEHX92RE3R0WVM43A38450XH0EHY6XYKCK0NWJJ9XKN0FG4GACRF6X1WG4Y7AYW6FBFMBKHWDHP8BCTT0J9Q9XXZEBC88C57DMG18ABP6S248JAX9JCN0GZ4Q25AX6XYA2W2KD7CW97H23H11NS47XMC0GFKF7FDZK11JQXASJMT","PJBV9WQ064WKS9WVV0JWN3JHGNCY82T42FC0ANKNV5SEE8BBPJTQ6HXM76D8FAH00A6GNB2HMRYAAS0ZTGESRE2JFDD5HFAWW2EX9BN1QA7T6EB1G4PBFFZXVD3AN3GZKK72A4VT5AFM9QF7GHEXVWC29AHH5HH3ZHYQ1AR5TB5G7CNRCM8GA1Q61P574H0G2DWJJ35XE79D3G6SEWPCPB3VZS89B3XY9A1HV86R6BVY6C48D7FHXEA903AVVSMP","7HS65FGXB1Y1E39DHB5J0SRX8KDS7K6YBNP1XAPBQQNBM8N2T1QAVVS37XG15A2ABWK9J2CP16WCWNMG1ZCS6KEY98DXRNKNEQ099M9P392D00R8HDE1Y8C918PV0DVXKM3141CYJFXTQCYM9A5ZYKDEXNF09NTWHNDT3F990R6EWYS0TT4F4YDW9605SWTPJ2G29CKQNP13N1KKFRJS4BME8VE800YH6HTCSWTPXQ19TFXCHET6PF9EZE00RF9D","39HPW4NZCE7T1TF3ZFW6FGSSSETD2NMY5ED7T49572WAZ5FBJJSGGK8G45CQSSS488KZ2WRVM2N79ZKD78AX08X7TAW7DGWKB5KX9KRFCAF7ZZJQP5W6A1652XYNV2NWR51MS30ME0Y6RPXCMVC7K2B3HK5TFQ893HE8JJJSD2TF3AS9RGMPRBXXPCTC1R3Y5MZJQ79RKV70PVW65MA00RPMC27VGBWW5MA7FWK4X8JWQ8PHB9666MVM64AZJZSC","WFPTT5ZYQZNF2TYKSMFT6D7RP85980PTXNP6PS1HY3J5Y4ZY8DEVNPGFDP85SQ2DST4QQ1EHQQ9CHP9T1Q25XSGNKAHF9QAXX6N029SNH3C0MTG5VAQWQMCSPAYBW08EKFJS560JXX1AT9JCD0CJXZTX1P7TC4XD1JJCBDYKQ592CJZDKBCVMDN2CAG7RNXVSKQ8EWCRM7NCEF3YVWBJ7SQRE9B3BB067SVSRJQ9SB0PTGRP93WRNQ2JEGC584K8","P0VHPX17WERWA5G19P5NV2ZBWXGX3YWFTYHMSWQ2C5H82TPQ08BNDPE813NYWWT231EMTY7VRW2ZS8S447T4K5BCD7S0YQX2D92G5VRP10YTC57P16QZ37T0QZ9JENVPT6665TZ97JERST3M8SG7BD3ZDM5H0S9KCQTXXDJCR9NB1WHNJFTYZ0DJYJFDNZMTKREQG3R5VF5DQXXWKJ2GVKZG6ED2VTJR11MKYTQBMXSKP50RJYEZMVCZQ8H5KZBV","VDS1SGX5A31KBPTBPBR82C4ZD5FG2XRDM5KGVAR7X8XGXP7ZMGREXS9CYFKA9SZ3GMF0H7X4H733C81HPNDDC2NPF4443CB0KY0XSMJ34SA6TY99SE0NTZ96RZ36CZP77HRQQ93WC2EN3FBGS9WQMZEQ7GKNZSH6RBGXMF237ZGRN4FPRGX771VXY5DQXFMPTFYRDHCPWJR9FZKF7TQT0MB1K412PRFHSHM9S8FZJCHYR2PP443WF9SZWGNM2CV8","J8KEHBYBAQVMTR1XTNDY23VSTQVW5CYE2EDNRE9DBP56DV1PSC2XXFH8KKHXT8D3TM5G6MCHE0JWNHN6417G696MHY5NZ6CRAYYBNR4YBPHDF94CHBMZSXH6KD3N0AVW3F0KK88DPA9G2CZ8C82QTV8Z8C8WRRWAHRH0X6SEGK4BJPEB3WEGRMD9AFYTXCPQ0N7BXK83052V4HD6W7AB0AE7EQ789XF6C8AD4TN7ARF7MJDMTCY624B6QR5K15Y5","R5D4QJ0HM2A9E8WEVZYKG9V612D1ZRRB6RCGK4S8MDBM7NRBP7DDJGV1H7SN0JEZJSTW7DGVA7WRK0Z8HSH6VGB2NZ2CRZKZTTX15W58EB2XKJKZJYA0AR5MKTNHPP7D39PFF8BP39PJX9VRFHGXMWB84GHVMND6S2M2WWVHMBVNK2D19TJ1240NZFV6GP64416JT0QW06ZQMXVHVD4GFRESTSVBH1G78AMJ972RAHBF4DM0PJXY0PTXYWQSP2GZ","KZ0R0MPKXYKS13894TAC8SMNGYQ9F1EB9VN9S4P7VJ8KDXJEREYR64HDRAP4HBRY9D50YDKDPE9AHMW9ETR4CXK3E0YQJQ2YGPAWJ823GH7KHJXK1GTFSZ3TERT1VB31FGXC0950TY0MAHESMPHNRXSG9738C0CEJ5Z1TNBVV48WJCH2JPCKAPDMTSFYFAECZ28WJ7J9EW459NNT5H4D0DB5ZZ2CY16CCC8PBC0K4ZYP1Y2ZW5MK0N0MJTXGQ49T","YVH178SVB7QK1HM2HVSDGBZ1QJN9NRTG3RXMC9YVS74VXTW719NRFMAXKJHVZJE77HNRW53S8DSD3QTYDZ9ZNZ1185GZXP0NNP40NQFYJQSBHWVE7WDPR5KZT42BWQE1P20B3F5HCHV3VD1ZCGCZ1NF5K5P4WKT01K7XKXNGFTX9HY5YAG30THBY4S3Q3CPMQBVCBXDHW929DHRBZWHZGGGBCTVNRRNDA7J0EVBAVYYHES2PDK9YW5STCG83S5AB","MA1RKAT9SCJSXJG7T5P1QF1VW9GRBCYK03P8Z55KSCK4SN1K0MFSXR3YERD4VA5QQ1N7KKHPDAPNYHQ6QW2TPBVM932A5G42VEKZ9CH5ZH7346RKDT8BMNSFYQ799W6042CCJ3JSESFYMER8JHZNBVFWFGMKMJJ642BCP9PPA9H7B0XSSGK2CZYWK3RQX5WKZK4V40AMTRJ9JRZNX7QKXRN9W464J3X3BRYHR8Y70C42HH0HVW86CPNVKWE0Q36C","K8CP0BM60F6P218ZMC4HW307DNN3FHHEG3XMWN55KEZR4RQ86ADXGJF2FKD7QW2TVQ3AWN2KVJ6QMP6SFJHF11C6T68FWKWMQVGQWYY28591XTQ3D78FB9N06T2W3R6EERTHRQNK7X9WP2BV3VM43A39YXVJ0W0BHJ0XSZQENN218ZR4GTVDX48788WXFD6064ZXGMWGD2EKWMS4BBVWK59FVZPGDRQD6EN3TRA784HBBWE831JWBEVX3YGPX9T0","QC2WWZPKJDCFZA65QKSGKW7YTAS9RP0W4H0FP96Z5TGCCD0K9FXFX4F75MQEDNKCM0B34D8ZR46CHR97HMMEB3W2T8W6P8X5289Z729365HVDQWCGHMJSGM7AHA1HS6ZND695X67D42H7XB7NQ5X57XG2S32R767YPQJERZ0ESG3S01NGNQ0P62KM3AT28JX41GMHPEA92V1Z80RYS8GWE9CGZ6K66NVGW72SN8DJGDFVC7771HP93X6DYN0TF63","H3P6CNRKH4BSCAWG3T9RRA89EBF1F91JEYEY4RZJQ2NNCZZASY362XHME0KNM36R4SNQD956CBW15XAXVK70TZM6TBVMHJ8KKQQQKBG4Q91ZHKAS10NEVD5B36QGYM7CMJM3DVJ00KTK0ZAM8C4YRNZDZKV5CNFGDR0DDP1P38704Y3G9KFRJ455SD39J7ZYDB5GQV17ZF2PE7GY927S1KSXV4KKXQ7FRX1VZRJGAD1F3DY4KJFRZJGAT5951JNS"]]} diff --git a/src/testing/baseline/refresh_reveal.req b/src/testing/baseline/refresh_reveal.req new file mode 100644 index 00000000..3fb14396 --- /dev/null +++ b/src/testing/baseline/refresh_reveal.req @@ -0,0 +1,7 @@ +POST /refresh/reveal HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 255 + +{"session_hash":"V97SB8T670M9V71D1Q0KVQ4GSJCVQ5AAKTTH9QKT0ZJZZBFNZAV4NA8NMWRRGVPFEBEGB6ANCN9BPQASJ40TM4Y1C49648TJJ07PGSG","transfer_privs":[["EQKJA401A9NJ2YJDFZJ1EV8AYXBHWZB6NT5T0TWSJHVKVDM6W8A0"],["TKDJ4DF3GZVG0DGAB9E3RGBGSTANYB6JVVWXJGPMB2AY4VQNTBA0"]]}
\ No newline at end of file diff --git a/src/testing/baseline/reserve_status.req b/src/testing/baseline/reserve_status.req new file mode 100644 index 00000000..4f988f66 --- /dev/null +++ b/src/testing/baseline/reserve_status.req @@ -0,0 +1,4 @@ +GET /reserve/status?reserve_pub=TMZCK5CFM1KZQGY1WTF0CEZZPGA0670G94969RF79PA5106ARTK0 HTTP/1.1 +Host: localhost:8081 +Accept: */* + diff --git a/src/testing/baseline/reserve_withdraw.req b/src/testing/baseline/reserve_withdraw.req new file mode 100644 index 00000000..48495025 --- /dev/null +++ b/src/testing/baseline/reserve_withdraw.req @@ -0,0 +1,7 @@ +POST /reserve/withdraw HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json +Content-Length: 919 + +{"coin_ev":"Q5X8A8TCBFH7E5BMY7HSB17SHFTM1JPJGV61P2CA7Z9EXG8P2HYS69B31NZESKXHSZHNJ2DQN3CC2AWFNC6V90J577JD3TXBMAY8Y5M9V60KKT73Z1DW24JFSNAK91G1F2WT55ADP1EG7N5F9AY7A7ZJD03MPYSH0RDP7SVZS2KRPA5JRHFR4GDJ59CFNE7A43M95ZKQHQAS8","denom_pub":"51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30GT58S2K2HJ16H336C9N8CVK4E9N6H1MADHH61330HHM6N1K8E1H8RVKJH256D1M6E1K8RWKJGSH8S2M6DJ170TK2H266GTK8DSS64RKJDJ26D144DJ474SK0GHQ711MAD9G752M2CJ58S1KJDA570SK2E9G8N23GCJ28S146DHH610K2H1Q8CW3GGA16S146H9G68TKACSQ6914CE1H691K2E9N6RWM8H9P8CWM2H9S8GSK0H9P6D1K6H9G6X0M4C2171144HJ46N334H9J692M4H9M8MR4CCJ46GRKEGA46533CDJ38MV4CH9K892MAH1P8S2K6D9K6N246E256H244G9Q6D346GJ56S23JGHJ690KADHJ8H242H2575132CSM6X1M4G9N6RR48E9H8MVM8E9354520818CMG26C1H60R30C935452081918G2J2G0","reserve_pub":"TMZCK5CFM1KZQGY1WTF0CEZZPGA0670G94969RF79PA5106ARTK0","reserve_sig":"8427B3RTB217124EB1C37ZVJFC08KN17RHGHE9ENZQMQVJ0S11SAX6H8Z06SWCKT06DRQ9DQ8XD786XKQ94T27PYR9GC9EMT1Y02W10"}
\ No newline at end of file diff --git a/src/testing/baseline/wire.req b/src/testing/baseline/wire.req new file mode 100644 index 00000000..a4f1d074 --- /dev/null +++ b/src/testing/baseline/wire.req @@ -0,0 +1,5 @@ +GET /wire HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json + diff --git a/src/testing/baseline/wire_sepa.req b/src/testing/baseline/wire_sepa.req new file mode 100644 index 00000000..80d3d461 --- /dev/null +++ b/src/testing/baseline/wire_sepa.req @@ -0,0 +1,5 @@ +GET /wire/sepa HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json + diff --git a/src/testing/baseline/wire_test.req b/src/testing/baseline/wire_test.req new file mode 100644 index 00000000..684352c9 --- /dev/null +++ b/src/testing/baseline/wire_test.req @@ -0,0 +1,5 @@ +GET /wire/test HTTP/1.1 +Host: localhost:8081 +Accept: */* +Content-Type: application/json + diff --git a/src/testing/test-taler-exchange-aggregator-postgres.conf b/src/testing/test-taler-exchange-aggregator-postgres.conf new file mode 100644 index 00000000..7f22acd2 --- /dev/null +++ b/src/testing/test-taler-exchange-aggregator-postgres.conf @@ -0,0 +1,98 @@ +[PATHS] +# Persistant data storage for the testcase +TALER_TEST_HOME = test_taler_exchange_httpd_home/ + +[taler] +# Currency supported by the exchange (can only be one) +CURRENCY = EUR + +[exchange] +# The DB plugin to use +DB = postgres + +# HTTP port the exchange listens to +PORT = 8081 + +# Master public key used to sign the exchange's various keys +MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG + +# Expected base URL of the exchange.  Used in wire transfers for +# the tracking API. +BASE_URL = "https://exchange.taler.net/" + +[auditor] +BASE_URL = "http://auditor.example.com/" + +[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 + +[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 + +[account-1] + +# What is the account URL? +URL = "payto://x-taler-bank/localhost/2" +WIRE_GATEWAY_URL = "http://localhost:8082/2/" +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = Exchange +PASSWORD = x +method = "x-taler-bank" +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json +ENABLE_DEBIT = YES +ENABLE_CREDIT = YES +TALER_BANK_AUTH_METHOD = NONE + +[bank] +HTTP_PORT = 8082 + +[fees-x-taler-bank] + +# Fees for the forseeable future... +# If you see this after 2018, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 diff --git a/src/testing/test-taler-exchange-wirewatch-postgres.conf b/src/testing/test-taler-exchange-wirewatch-postgres.conf new file mode 100644 index 00000000..ae5cd7ef --- /dev/null +++ b/src/testing/test-taler-exchange-wirewatch-postgres.conf @@ -0,0 +1,105 @@ +[PATHS] +# Persistant data storage for the testcase +TALER_TEST_HOME = test_taler_exchange_httpd_home/ + +[taler] +# Currency supported by the exchange (can only be one) +CURRENCY = EUR + +[exchange] +# The DB plugin to use +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 + +[account-1] + +# What is the account URL? +URL = "payto://x-taler-bank/localhost/2" +WIRE_GATEWAY_URL = "http://localhost:8082/2/" + +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = Exchange +PASSWORD = x + +METHOD = x-taler-bank +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json +PLUGIN = "taler_bank" +ENABLE_DEBIT = YES +ENABLE_CREDIT = YES +TALER_BANK_AUTH_METHOD = NONE + +[bank] +HTTP_PORT = 8082 + +[fees-x-taler-bank] + +# Fees for the forseeable future... +# If you see this after 2018, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 + +# Need at least one coin, otherwise Exchange +# refuses to start. +[coin_eur_ct_1] +value = EUR:0.01 +duration_overlap = 5 minutes +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 + diff --git a/src/testing/test_auditor_api.c b/src/testing/test_auditor_api.c new file mode 100644 index 00000000..92236102 --- /dev/null +++ b/src/testing/test_auditor_api.c @@ -0,0 +1,710 @@ +/* +  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 testing/test_auditor_api.c + * @brief testcase to test auditor's HTTP API interface + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_auditor_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" + + +/** + * Configuration file we use.  One (big) configuration is used + * for the various components for this test. + */ +#define CONFIG_FILE "test_auditor_api.conf" + +#define CONFIG_FILE_EXPIRE_RESERVE_NOW \ +  "test_auditor_api_expire_reserve_now.conf" + +/** + * Exchange configuration data. + */ +static struct TALER_TESTING_ExchangeConfiguration ec; + +/** + * Bank configuration data. + */ +static struct TALER_TESTING_BankConfiguration bc; + +/** + * 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_wirewatch (label, CONFIG_FILE) + +/** + * Execute the taler-exchange-aggregator command with + * our configuration file. + * + * @param label label to use for the command. + */ +#define CMD_EXEC_AGGREGATOR(label) \ +  TALER_TESTING_cmd_exec_aggregator (label, CONFIG_FILE) + +/** + * Run wire transfer of funds from some user's account to the + * exchange. + * + * @param label label to use for the command. + * @param amount amount to transfer, i.e. "EUR:1" + */ +#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \ +  TALER_TESTING_cmd_admin_add_incoming (label, amount,           \ +                                        &bc.exchange_auth,       \ +                                        bc.user42_payto) + +/** + * Run the taler-auditor. + * + * @param label label to use for the command. + */ +#define CMD_RUN_AUDITOR(label) \ +  TALER_TESTING_cmd_exec_auditor (label, CONFIG_FILE) + +/** + * Run the taler-wire-auditor. + * + * @param label label to use for the command. + */ +#define CMD_RUN_WIRE_AUDITOR(label) \ +  TALER_TESTING_cmd_exec_wire_auditor (label, CONFIG_FILE) + + +/** + * Main function that will tell the interpreter what commands to + * run. + * + * @param cls closure + */ +static void +run (void *cls, +     struct TALER_TESTING_Interpreter *is) +{ +  /** +   * Test withdraw. +   */ +  struct TALER_TESTING_Command withdraw[] = { +    /** +     * Move money to the exchange's bank account. +     */ +    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1", +                              "EUR:5.01"), +    TALER_TESTING_cmd_check_bank_admin_transfer +      ("check-create-reserve-1", +      "EUR:5.01", bc.user42_payto, bc.exchange_payto, +      "create-reserve-1"), +    /** +     * Make a reserve exist, according to the previous transfer. +     */ +    CMD_EXEC_WIREWATCH ("wirewatch-1"), +    /** +     * Withdraw EUR:5. +     */ +    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", +                                       "create-reserve-1", +                                       "EUR:5", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_end () +  }; + +  struct TALER_TESTING_Command spend[] = { +    /** +     * Spend the coin. +     */ +    TALER_TESTING_cmd_deposit ("deposit-simple", +                               "withdraw-coin-1", +                               0, +                               bc.user42_payto, +                               "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:5", +                               MHD_HTTP_OK), +    TALER_TESTING_cmd_end () +  }; + +  struct TALER_TESTING_Command refresh[] = { +    /* Fill reserve with EUR:5, 1ct is for fees.  NOTE: the old +     * test-suite gave a account number of _424_ to the user at +     * this step; to type less, here the _42_ number is reused. +     * Does this change the tests semantics? */// +    CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve-1", +                              "EUR:5.01"), +    TALER_TESTING_cmd_check_bank_admin_transfer +      ("check-refresh-create-reserve-1", +      "EUR:5.01", bc.user42_payto, bc.exchange_payto, +      "refresh-create-reserve-1"), +    /** +     * Make previous command effective. +     */ +    CMD_EXEC_WIREWATCH ("wirewatch-2"), +    /** +     * Withdraw EUR:5. +     */ +    TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1", +                                       "refresh-create-reserve-1", +                                       "EUR:5", +                                       MHD_HTTP_OK), +    /** +     * Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in +     * full) Merchant receives EUR:0.99 due to 1 ct deposit fee. +     */ +    TALER_TESTING_cmd_deposit ("refresh-deposit-partial", +                               "refresh-withdraw-coin-1", +                               0, +                               bc.user42_payto, +                               "{\"items\":[{\"name\":\"ice\",\"value\":\"EUR:1\"}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:1", +                               MHD_HTTP_OK), +    /** +     * Melt the rest of the coin's value (EUR:4.00 = 3x EUR:1.03 + 7x +     * EUR:0.13) */ +    TALER_TESTING_cmd_refresh_melt_double ("refresh-melt-1", +                                           "refresh-withdraw-coin-1", +                                           MHD_HTTP_OK, +                                           NULL), +    /** +     * Complete (successful) melt operation, and withdraw the coins +     */ +    TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-1", +                                      "refresh-melt-1", +                                      MHD_HTTP_OK), +    /** +     * Try to spend a refreshed EUR:0.1 coin +     */ +    TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-1b", +                               "refresh-reveal-1", +                               3, +                               bc.user43_payto, +                               "{\"items\":[{\"name\":\"ice cream\",\"value\":3}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:0.1", +                               MHD_HTTP_OK), +    TALER_TESTING_cmd_end () +  }; + +  struct TALER_TESTING_Command track[] = { +    /** +     * Run transfers. Note that _actual_ aggregation will NOT +     * happen here, as each deposit operation is run with a +     * fresh merchant public key! NOTE: this comment comes +     * "verbatim" from the old test-suite, and IMO does not explain +     * a lot!*/// +    CMD_EXEC_AGGREGATOR ("run-aggregator"), + +    /** +     * Check all the transfers took place. +     */ +    TALER_TESTING_cmd_check_bank_transfer +      ("check_bank_transfer-499c", ec.exchange_url, +      "EUR:4.98", bc.exchange_payto, bc.user42_payto), +    TALER_TESTING_cmd_check_bank_transfer +      ("check_bank_transfer-99c1", ec.exchange_url, +      "EUR:0.98", bc.exchange_payto, bc.user42_payto), +    TALER_TESTING_cmd_check_bank_transfer +      ("check_bank_transfer-99c", ec.exchange_url, +      "EUR:0.08", bc.exchange_payto, bc.user43_payto), + +    /* The following transactions got originated within +     * the "massive deposit confirms" batch.  */ +    TALER_TESTING_cmd_check_bank_transfer +      ("check-massive-transfer-1", +      ec.exchange_url, +      "EUR:0.98", +      bc.exchange_payto, bc.user43_payto), +    TALER_TESTING_cmd_check_bank_transfer +      ("check-massive-transfer-2", +      ec.exchange_url, +      "EUR:0.98", +      bc.exchange_payto, bc.user43_payto), +    TALER_TESTING_cmd_check_bank_transfer +      ("check-massive-transfer-3", +      ec.exchange_url, +      "EUR:0.98", +      bc.exchange_payto, bc.user43_payto), +    TALER_TESTING_cmd_check_bank_transfer +      ("check-massive-transfer-4", +      ec.exchange_url, +      "EUR:0.98", +      bc.exchange_payto, bc.user43_payto), +    TALER_TESTING_cmd_check_bank_transfer +      ("check-massive-transfer-5", +      ec.exchange_url, +      "EUR:0.98", +      bc.exchange_payto, bc.user43_payto), +    TALER_TESTING_cmd_check_bank_transfer +      ("check-massive-transfer-6", +      ec.exchange_url, +      "EUR:0.98", +      bc.exchange_payto, bc.user43_payto), +    TALER_TESTING_cmd_check_bank_transfer +      ("check-massive-transfer-7", +      ec.exchange_url, +      "EUR:0.98", +      bc.exchange_payto, bc.user43_payto), +    TALER_TESTING_cmd_check_bank_transfer +      ("check-massive-transfer-8", +      ec.exchange_url, +      "EUR:0.98", +      bc.exchange_payto, bc.user43_payto), +    TALER_TESTING_cmd_check_bank_transfer +      ("check-massive-transfer-9", +      ec.exchange_url, +      "EUR:0.98", +      bc.exchange_payto, bc.user43_payto), +    TALER_TESTING_cmd_check_bank_transfer +      ("check-massive-transfer-10", +      ec.exchange_url, +      "EUR:0.98", +      bc.exchange_payto, bc.user43_payto), +    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"), +    TALER_TESTING_cmd_end () +  }; + +  /** +   * This block checks whether a wire deadline +   * very far in the future does NOT get aggregated now. +   */ +  struct TALER_TESTING_Command unaggregation[] = { +    TALER_TESTING_cmd_check_bank_empty ("far-future-aggregation-a"), +    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-unaggregated", +                              "EUR:5.01"), +    CMD_EXEC_WIREWATCH ("wirewatch-unaggregated"), +    /* "consume" reserve creation transfer.  */ +    TALER_TESTING_cmd_check_bank_admin_transfer ( +      "check_bank_transfer-unaggregated", +      "EUR:5.01", +      bc.user42_payto, +      bc.exchange_payto, +      "create-reserve-unaggregated"), +    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated", +                                       "create-reserve-unaggregated", +                                       "EUR:5", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit ("deposit-unaggregated", +                               "withdraw-coin-unaggregated", +                               0, +                               bc.user43_payto, +                               "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +                               GNUNET_TIME_relative_multiply +                                 (GNUNET_TIME_UNIT_YEARS, +                                 3000), +                               "EUR:5", +                               MHD_HTTP_OK), +    CMD_EXEC_AGGREGATOR ("aggregation-attempt"), +    TALER_TESTING_cmd_check_bank_empty ("far-future-aggregation-b"), +    TALER_TESTING_cmd_end () +  }; + +  struct TALER_TESTING_Command refund[] = { +    /** +     * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config. +     */ +    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-r1", +                              "EUR:5.01"), +    /** +     * Run wire-watch to trigger the reserve creation. +     */ +    CMD_EXEC_WIREWATCH ("wirewatch-3"), +    /** +     * Withdraw a 5 EUR coin, at fee of 1 ct +     */ +    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1", +                                       "create-reserve-r1", +                                       "EUR:5", +                                       MHD_HTTP_OK), +    /** +     * Spend 5 EUR of the 5 EUR coin (in full). Merchant would +     * receive EUR:4.99 due to 1 ct deposit fee. +     */ +    TALER_TESTING_cmd_deposit ("deposit-refund-1", +                               "withdraw-coin-r1", +                               0, +                               bc.user42_payto, +                               "{\"items\":[{\"name\":\"ice\",\"value\":\"EUR:5\"}]}", +                               GNUNET_TIME_UNIT_MINUTES, +                               "EUR:5", +                               MHD_HTTP_OK), + +    TALER_TESTING_cmd_refund ("refund-ok", +                              MHD_HTTP_OK, +                              "EUR:5", +                              "EUR:0.01", +                              "deposit-refund-1"), +    /** +     * Spend 4.99 EUR of the refunded 4.99 EUR coin (1ct gone +     * due to refund) (merchant would receive EUR:4.98 due to +     * 1 ct deposit fee) */ +    TALER_TESTING_cmd_deposit ("deposit-refund-2", +                               "withdraw-coin-r1", +                               0, +                               bc.user42_payto, +                               "{\"items\":[{\"name\":\"more\",\"value\":\"EUR:5\"}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:4.99", +                               MHD_HTTP_OK), +    /** +     * Run transfers. This will do the transfer as refund deadline was +     * 0. +     */ +    CMD_EXEC_AGGREGATOR ("run-aggregator-3"), +    TALER_TESTING_cmd_end () +  }; + +  struct TALER_TESTING_Command recoup[] = { +    /** +     * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per +     * config. +     */ +    CMD_TRANSFER_TO_EXCHANGE ("recoup-create-reserve-1", +                              "EUR:5.01"), +    /** +     * Run wire-watch to trigger the reserve creation. +     */ +    CMD_EXEC_WIREWATCH ("wirewatch-4"), +    /** +     * Withdraw a 5 EUR coin, at fee of 1 ct +     */ +    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1", +                                       "recoup-create-reserve-1", +                                       "EUR:5", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_revoke ("revoke-1", +                              MHD_HTTP_OK, +                              "recoup-withdraw-coin-1", +                              CONFIG_FILE), +    TALER_TESTING_cmd_recoup ("recoup-1", +                              MHD_HTTP_OK, +                              "recoup-withdraw-coin-1", +                              "EUR:5", +                              NULL), +    /** +     * Re-withdraw from this reserve +     */ +    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2", +                                       "recoup-create-reserve-1", +                                       "EUR:1", +                                       MHD_HTTP_OK), +    /** +     * These commands should close the reserve because the aggregator +     * is given a config file that ovverrides the reserve expiration +     * time (making it now-ish) +     */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_aggregator ("close-reserves", +                                       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 +     * 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...) +     */CMD_TRANSFER_TO_EXCHANGE ("recoup-create-reserve-2", +                              "EUR:2.02"), +    /** +     * Make previous command effective. +     */ +    CMD_EXEC_WIREWATCH ("wirewatch-5"), +    /** +     * Withdraw a 1 EUR coin, at fee of 1 ct +     */ +    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2a", +                                       "recoup-create-reserve-2", +                                       "EUR:1", +                                       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", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit ("recoup-deposit-partial", +                               "recoup-withdraw-coin-2a", +                               0, +                               bc.user42_payto, +                               "{\"items\":[{\"name\":\"more ice cream\",\"value\":1}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:0.5", +                               MHD_HTTP_OK), +    TALER_TESTING_cmd_revoke ("revoke-2", +                              MHD_HTTP_OK, +                              "recoup-withdraw-coin-2a", +                              CONFIG_FILE), +    TALER_TESTING_cmd_recoup ("recoup-2", +                              MHD_HTTP_OK, +                              "recoup-withdraw-coin-2a", +                              "EUR:0.5", +                              NULL), +    TALER_TESTING_cmd_end () +  }; + + +  struct TALER_TESTING_Command massive_deposit_confirms[] = { + +    /** +     * Move money to the exchange's bank account. +     */ +    CMD_TRANSFER_TO_EXCHANGE ("massive-reserve", +                              "EUR:10.10"), +    TALER_TESTING_cmd_check_bank_admin_transfer +      ("check-massive-transfer", +      "EUR:10.10", +      bc.user42_payto, bc.exchange_payto, +      "massive-reserve"), +    CMD_EXEC_WIREWATCH ("massive-wirewatch"), +    TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-1", +                                       "massive-reserve", +                                       "EUR:1", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-2", +                                       "massive-reserve", +                                       "EUR:1", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-3", +                                       "massive-reserve", +                                       "EUR:1", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-4", +                                       "massive-reserve", +                                       "EUR:1", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-5", +                                       "massive-reserve", +                                       "EUR:1", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-6", +                                       "massive-reserve", +                                       "EUR:1", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-7", +                                       "massive-reserve", +                                       "EUR:1", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-8", +                                       "massive-reserve", +                                       "EUR:1", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-9", +                                       "massive-reserve", +                                       "EUR:1", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-10", +                                       "massive-reserve", +                                       "EUR:1", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit +      ("massive-deposit-1", +      "massive-withdraw-1", +      0, +      bc.user43_payto, +      "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +      GNUNET_TIME_UNIT_ZERO, +      "EUR:1", +      MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit +      ("massive-deposit-2", +      "massive-withdraw-2", +      0, +      bc.user43_payto, +      "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +      GNUNET_TIME_UNIT_ZERO, +      "EUR:1", +      MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit +      ("massive-deposit-3", +      "massive-withdraw-3", +      0, +      bc.user43_payto, +      "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +      GNUNET_TIME_UNIT_ZERO, +      "EUR:1", +      MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit +      ("massive-deposit-4", +      "massive-withdraw-4", +      0, +      bc.user43_payto, +      "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +      GNUNET_TIME_UNIT_ZERO, +      "EUR:1", +      MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit +      ("massive-deposit-5", +      "massive-withdraw-5", +      0, +      bc.user43_payto, +      "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +      GNUNET_TIME_UNIT_ZERO, +      "EUR:1", +      MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit +      ("massive-deposit-6", +      "massive-withdraw-6", +      0, +      bc.user43_payto, +      "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +      GNUNET_TIME_UNIT_ZERO, +      "EUR:1", +      MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit +      ("massive-deposit-7", +      "massive-withdraw-7", +      0, +      bc.user43_payto, +      "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +      GNUNET_TIME_UNIT_ZERO, +      "EUR:1", +      MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit +      ("massive-deposit-8", +      "massive-withdraw-8", +      0, +      bc.user43_payto, +      "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +      GNUNET_TIME_UNIT_ZERO, +      "EUR:1", +      MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit +      ("massive-deposit-9", +      "massive-withdraw-9", +      0, +      bc.user43_payto, +      "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +      GNUNET_TIME_UNIT_ZERO, +      "EUR:1", +      MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit +      ("massive-deposit-10", +      "massive-withdraw-10", +      0, +      bc.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, +                                            "EUR:0.99", +                                            MHD_HTTP_OK), +    CMD_RUN_AUDITOR ("massive-auditor"), + +    TALER_TESTING_cmd_end () +  }; + +  struct TALER_TESTING_Command commands[] = { +    CMD_RUN_AUDITOR ("virgin-auditor"), +    CMD_RUN_WIRE_AUDITOR ("virgin-wire-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", +                             withdraw), +    TALER_TESTING_cmd_batch ("spend", +                             spend), +    TALER_TESTING_cmd_batch ("refresh", +                             refresh), +    TALER_TESTING_cmd_batch ("track", +                             track), +    TALER_TESTING_cmd_batch ("unaggregation", +                             unaggregation), +    TALER_TESTING_cmd_batch ("refund", +                             refund), +    TALER_TESTING_cmd_batch ("recoup", +                             recoup), +    CMD_RUN_AUDITOR ("normal-auditor"), +    CMD_RUN_WIRE_AUDITOR ("normal-wire-auditor"), +    TALER_TESTING_cmd_end () +  }; + +  TALER_TESTING_run_with_fakebank (is, +                                   commands, +                                   bc.exchange_auth.wire_gateway_url); +} + + +int +main (int argc, +      char *const *argv) +{ +  /* These environment variables get in the way... */ +  unsetenv ("XDG_DATA_HOME"); +  unsetenv ("XDG_CONFIG_HOME"); +  GNUNET_log_setup ("test-auditor-api", +                    "INFO", +                    NULL); +  /* Check fakebank port is available and get configuration data. */ +  if (GNUNET_OK != +      TALER_TESTING_prepare_fakebank (CONFIG_FILE, +                                      "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, +                                          &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; +  } +  return 0; +} + + +/* end of test_auditor_api.c */ diff --git a/src/testing/test_auditor_api.conf b/src/testing/test_auditor_api.conf new file mode 100644 index 00000000..7bb57c3a --- /dev/null +++ b/src/testing/test_auditor_api.conf @@ -0,0 +1,210 @@ + +# This file is in the public domain. +# +[PATHS] +# Persistant data storage for the testcase +TALER_TEST_HOME = test_exchange_api_home/ + +[taler] +# Currency supported by the exchange (can only be one) +CURRENCY = EUR + +[auditor] +BASE_URL = "http://localhost:8083/" + +# HTTP port the auditor listens to +PORT = 8083 + +TINY_AMOUNT = EUR:0.01 + +[exchange] + +# how long is one signkey valid? +signkey_duration = 4 weeks + +# how long are the signatures with the signkey valid? +legal_duration = 2 years + +# how long do we provide to clients denomination and signing keys +# ahead of time? +lookahead_provide = 4 weeks 1 day + +# HTTP port the exchange listens to +PORT = 8081 + +# Master public key used to sign the exchange's various keys +MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG + +# How to access our database +DB = postgres + +# Base URL of the exchange. Must be set to a URL where the +# exchange (or the twister) is actually listening. +BASE_URL = "http://localhost:8081/" + +# Keep it short so the test runs fast. +LOOKAHEAD_SIGN = 12 h + +[exchangedb-postgres] +CONFIG = "postgres:///talercheck" + +[auditordb-postgres] +CONFIG = "postgres:///talercheck" + +# Sections starting with "account-" configure the bank accounts +# of the exchange.  The "URL" specifies the account in +# payto://-format, while the WIRE_JSON specifies the +# (possibly offline) signed version to be returned in /wire. +# WIRE_JSON is optional, as not all accounts must be +# advertised in /wire. +[account-1] +# What is the URL of our account? +URL = "payto://x-taler-bank/localhost/42" +WIRE_GATEWAY_URL = "http://localhost:8082/42/" +# This is the response we give out for the /wire request.  It provides +# wallets with the bank information for transfers to the exchange. +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json + +# Which wire plugin should we used to access the account? +METHOD = x-taler-bank + +[bank] +HTTP_PORT = 8082 + +# ENABLE_CREDIT = YES + +[account-2] +# What is the bank account (with the "Taler Bank" demo system)? +WIRE_GATEWAY_URL = "http://localhost:8082/2/" +URL = "payto://x-taler-bank/localhost/2" + +# This is the response we give out for the /wire request.  It provides +# wallets with the bank information for transfers to the exchange. +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-2.json + +# Which wire plugin should we used to access the account? +METHOD = x-taler-bank + +# Authentication information for basic authentication +WIRE_GATEWAY_AUTH_METHOD = "basic" +USERNAME = user +PASSWORD = pass + +ENABLE_DEBIT = YES + +ENABLE_CREDIT = YES + + +# Sections starting with "fee-" configure the wire fee for the +# respective wire method. +[fees-iban] +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 + +[fees-x-taler-bank] +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 + +# 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_overlap = 5 minutes +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_overlap = 5 minutes +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_overlap = 5 minutes +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_overlap = 5 minutes +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_overlap = 5 minutes +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.conf new file mode 100644 index 00000000..c2bf8f47 --- /dev/null +++ b/src/testing/test_auditor_api_expire_reserve_now.conf @@ -0,0 +1,4 @@ +@INLINE@ test_auditor_api.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 new file mode 100644 index 00000000..62277e82 --- /dev/null +++ b/src/testing/test_auditor_api_version.c @@ -0,0 +1,163 @@ +/* +  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 testing/test_auditor_api_version.c + * @brief testcase to test auditor's HTTP API interface to fetch /version + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_auditor_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" + + +/** + * Configuration file we use.  One (big) configuration is used + * for the various components for this test. + */ +#define CONFIG_FILE "test_auditor_api.conf" + +static struct TALER_AUDITOR_Handle *ah; + +static struct GNUNET_CURL_Context *ctx; + +static struct GNUNET_CURL_RescheduleContext *rc; + +static int global_ret; + +static struct GNUNET_SCHEDULER_Task *tt; + +static void +do_shutdown (void *cls) +{ +  (void) cls; + +  if (NULL != tt) +  { +    GNUNET_SCHEDULER_cancel (tt); +    tt = NULL; +  } +  TALER_AUDITOR_disconnect (ah); +  GNUNET_CURL_fini (ctx); +  GNUNET_CURL_gnunet_rc_destroy (rc); +} + + +static void +do_timeout (void *cls) +{ +  (void) cls; +  tt = NULL; +  global_ret = 3; +  GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Function called with information about the auditor. + * + * @param cls closure + * @param vi basic information about the auditor + * @param compat protocol compatibility information + */ +static void +version_cb (void *cls, +            const struct TALER_AUDITOR_VersionInformation *vi, +            enum TALER_AUDITOR_VersionCompatibility compat) +{ +  if ( (NULL != vi) && +       (TALER_AUDITOR_VC_MATCH == compat) ) +    global_ret = 0; +  else +    global_ret = 2; +  GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Main function that will tell the interpreter what commands to + * run. + * + * @param cls closure + */ +static void +run (void *cls) +{ +  const char *auditor_url = "http://localhost:8083/"; + +  (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); +  GNUNET_SCHEDULER_add_shutdown (&do_shutdown, +                                 NULL); +  tt = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, +                                     &do_timeout, +                                     NULL); +} + + +int +main (int argc, +      char *const *argv) +{ +  struct GNUNET_OS_Process *proc; + +  /* These environment variables get in the way... */ +  unsetenv ("XDG_DATA_HOME"); +  unsetenv ("XDG_CONFIG_HOME"); +  GNUNET_log_setup ("test-auditor-api-version", +                    "INFO", +                    NULL); +  proc = GNUNET_OS_start_process (GNUNET_NO, +                                  GNUNET_OS_INHERIT_STD_ALL, +                                  NULL, NULL, NULL, +                                  "taler-auditor-httpd", +                                  "taler-auditor-httpd", +                                  "-c", CONFIG_FILE, +                                  NULL); +  if (NULL == proc) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to run `taler-auditor-httpd`," +                " is your PATH correct?\n"); +    return 77; +  } +  GNUNET_SCHEDULER_run (&run, +                        NULL); +  GNUNET_OS_process_kill (proc, SIGTERM); +  GNUNET_OS_process_wait (proc); +  GNUNET_OS_process_destroy (proc); +  return global_ret; +} + + +/* end of test_auditor_api_version.c */ diff --git a/src/testing/test_bank_api.c b/src/testing/test_bank_api.c new file mode 100644 index 00000000..e7d2aefa --- /dev/null +++ b/src/testing/test_bank_api.c @@ -0,0 +1,186 @@ +/* +  This file is part of TALER +  Copyright (C) 2016-2020 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as +  published by the Free Software Foundation; either version 3, +  or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, +  but WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, +  see <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/test_bank_api.c + * @brief testcase to test bank's HTTP API + *        interface against the fakebank + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_bank_service.h" +#include "taler_exchange_service.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> +#include <microhttpd.h> +#include "taler_testing_lib.h" + +#define CONFIG_FILE_FAKEBANK "test_bank_api_fakebank.conf" +#define CONFIG_FILE_PYBANK "test_bank_api_pybank.conf" + +/** + * Bank configuration data. + */ +static struct TALER_TESTING_BankConfiguration bc; + +/** + * Handle to the Py-bank daemon. + */ +static struct GNUNET_OS_Process *bankd; + +/** + * Flag indicating whether the test is running against the + * Fakebank.  Set up at runtime. + */ +static int with_fakebank; + +/** + * Main function that will tell the interpreter what commands to + * run. + * + * @param cls closure + */ +static void +run (void *cls, +     struct TALER_TESTING_Interpreter *is) +{ +  struct TALER_WireTransferIdentifierRawP wtid; + +  memset (&wtid, 42, sizeof (wtid)); + +  { +    struct TALER_TESTING_Command commands[] = { +      TALER_TESTING_cmd_bank_credits ("history-0", +                                      &bc.exchange_auth, +                                      NULL, +                                      1), +      TALER_TESTING_cmd_admin_add_incoming ("credit-1", +                                            "KUDOS:5.01", +                                            &bc.exchange_auth, +                                            bc.user42_payto), +      TALER_TESTING_cmd_bank_credits ("history-1c", +                                      &bc.exchange_auth, +                                      NULL, +                                      5), +      TALER_TESTING_cmd_bank_debits ("history-1d", +                                     &bc.exchange_auth, +                                     NULL, +                                     5), +      TALER_TESTING_cmd_admin_add_incoming ("credit-2", +                                            "KUDOS:3.21", +                                            &bc.exchange_auth, +                                            bc.user42_payto), +      TALER_TESTING_cmd_transfer ("debit-1", +                                  "KUDOS:3.22", +                                  &bc.exchange_auth, +                                  bc.exchange_payto, +                                  bc.user42_payto, +                                  &wtid, +                                  "http://exchange.example.com/"), +      TALER_TESTING_cmd_bank_debits ("history-2b", +                                     &bc.exchange_auth, +                                     NULL, +                                     5), +      TALER_TESTING_cmd_end () +    }; + +    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); +  } +} + + +int +main (int argc, +      char *const *argv) +{ +  int rv; +  const char *cfgfilename; + +  /* 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) +  { +    TALER_LOG_DEBUG ("Running against the Fakebank.\n"); +    cfgfilename = CONFIG_FILE_FAKEBANK; +    if (GNUNET_OK != +        TALER_TESTING_prepare_fakebank (CONFIG_FILE_FAKEBANK, +                                        "account-2", +                                        &bc)) +    { +      GNUNET_break (0); +      return 77; +    } +  } +  else +  { +    TALER_LOG_DEBUG ("Running against the Pybank.\n"); +    cfgfilename = CONFIG_FILE_PYBANK; +    if (GNUNET_OK != +        TALER_TESTING_prepare_bank (CONFIG_FILE_PYBANK, +                                    "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; +    } +  } + +  rv = (GNUNET_OK == TALER_TESTING_setup (&run, +                                          NULL, +                                          cfgfilename, +                                          NULL, +                                          GNUNET_NO)) ? 0 : 1; +  if (GNUNET_NO == with_fakebank) +  { + +    GNUNET_OS_process_kill (bankd, +                            SIGKILL); +    GNUNET_OS_process_wait (bankd); +    GNUNET_OS_process_destroy (bankd); +  } +  return rv; +} + + +/* end of test_bank_api.c */ diff --git a/src/testing/test_bank_api_fakebank.conf b/src/testing/test_bank_api_fakebank.conf new file mode 100644 index 00000000..5c9e5e3a --- /dev/null +++ b/src/testing/test_bank_api_fakebank.conf @@ -0,0 +1,17 @@ +# This file is in the public domain. + +[taler] +currency = KUDOS + +[account-2] +URL = payto://x-taler-bank/localhost/2 +METHOD = x-taler-bank +WIRE_GATEWAY_URL = "http://localhost:8081/2/" +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = Exchange +PASSWORD = x + +[bank] +SERVE = http +HTTP_PORT = 8081 +DATABASE = postgres:///talercheck diff --git a/src/testing/test_bank_api_fakebank_twisted.conf b/src/testing/test_bank_api_fakebank_twisted.conf new file mode 100644 index 00000000..4455ac74 --- /dev/null +++ b/src/testing/test_bank_api_fakebank_twisted.conf @@ -0,0 +1,34 @@ + +[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 + +[taler] +currency = KUDOS + +[bank] +serve = http +http_port = 8081 +database = postgres:///talercheck + +[account-1] +URL = payto://x-taler-bank/localhost:8081/1 + +[account-2] +URL = payto://x-taler-bank/localhost:8081/2 diff --git a/src/testing/test_bank_api_pybank.conf b/src/testing/test_bank_api_pybank.conf new file mode 100644 index 00000000..1d5f4a2d --- /dev/null +++ b/src/testing/test_bank_api_pybank.conf @@ -0,0 +1,17 @@ +# This file is in the public domain. + +[taler] +currency = KUDOS + +[account-2] +URL = payto://x-taler-bank/localhost/Exchange +METHOD = x-taler-bank +WIRE_GATEWAY_URL = "http://localhost:8081/taler-wire-gateway/Exchange/" +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = Exchange +PASSWORD = x + +[bank] +SERVE = http +HTTP_PORT = 8081 +DATABASE = postgres:///talercheck diff --git a/src/testing/test_bank_api_pybank_twisted.conf b/src/testing/test_bank_api_pybank_twisted.conf new file mode 100644 index 00000000..a2085c38 --- /dev/null +++ b/src/testing/test_bank_api_pybank_twisted.conf @@ -0,0 +1,46 @@ +[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 + + +[taler] +currency = KUDOS + + +[bank] +serve = http +http_port = 8081 +database = postgres:///talercheck + + +[account-1] +URL = payto://x-taler-bank/localhost/1 + + +[account-2] +URL = payto://x-taler-bank/localhost/Exchange +METHOD = x-taler-bank +WIRE_GATEWAY_URL = "http://localhost:8888/taler-wire-gateway/Exchange/" +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = Exchange +PASSWORD = x + + +[bank] +HTTP_PORT = 8081 diff --git a/src/testing/test_bank_api_twisted.c b/src/testing/test_bank_api_twisted.c new file mode 100644 index 00000000..fc167c1e --- /dev/null +++ b/src/testing/test_bank_api_twisted.c @@ -0,0 +1,221 @@ +/* +  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 testing/test_bank_api_with_fakebank_twisted.c + * @author Marcello Stanisci + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" +#include <taler/taler_twister_testing_lib.h> +#include <taler/taler_twister_service.h> + +/** + * Configuration file we use.  One (big) configuration is used + * for the various components for this test. + */ +#define CONFIG_FILE_FAKEBANK "test_bank_api_fakebank_twisted.conf" + +/** + * Separate config file for running with the pybank. + */ +#define CONFIG_FILE_PYBANK "test_bank_api_pybank_twisted.conf" + +/** + * True when the test runs against Fakebank. + */ +static int with_fakebank; + +/** + * Bank configuration data. + */ +static struct TALER_TESTING_BankConfiguration bc; + +/** + * (real) Twister URL.  Used at startup time to check if it runs. + */ +static char *twister_url; + +/** + * Twister process. + */ +static struct GNUNET_OS_Process *twisterd; + +/** + * Python bank process handle. + */ +static struct GNUNET_OS_Process *bankd; + + +/** + * Main function that will tell + * the interpreter what commands to run. + * + * @param cls closure + */ +static void +run (void *cls, +     struct TALER_TESTING_Interpreter *is) +{ + +  struct TALER_TESTING_Command commands[] = { +    /** +     * Can't use the "wait service" CMD here because the +     * fakebank runs inside the same process of the test. +     */ +    TALER_TESTING_cmd_wait_service ("wait-service", +                                    twister_url), +    TALER_TESTING_cmd_bank_credits ("history-0", +                                    &bc.exchange_auth, +                                    NULL, +                                    5), +    TALER_TESTING_cmd_end () +  }; + +  if (GNUNET_YES == with_fakebank) +    TALER_TESTING_run_with_fakebank (is, +                                     commands, +                                     bc.exchange_auth.wire_gateway_url); +  else +    TALER_TESTING_run (is, +                       commands); +} + + +/** + * Kill, wait, and destroy convenience function. + * + * @param process process to purge. + */ +static void +purge_process (struct GNUNET_OS_Process *process) +{ +  GNUNET_OS_process_kill (process, SIGINT); +  GNUNET_OS_process_wait (process); +  GNUNET_OS_process_destroy (process); +} + + +int +main (int argc, +      char *const *argv) +{ +  unsigned int ret; +  const char *cfgfilename; + +  /* These environment variables get in the way... */ +  unsetenv ("XDG_DATA_HOME"); +  unsetenv ("XDG_CONFIG_HOME"); +  GNUNET_log_setup ("test-bank-api-with-(fake)bank-twisted", +                    "DEBUG", +                    NULL); + +  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_TESTING_prepare_twister +                               (cfgfilename))) +  { +    GNUNET_break (0); +    return 77; +  } +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "twister_url is %s\n", +              twister_url); +  if (NULL == (twisterd = TALER_TESTING_run_twister (cfgfilename))) +  { +    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, +                                        "account-2", +                                        &bc)) +    { +      GNUNET_break (0); +      GNUNET_free (twister_url); +      return 77; +    } +  } +  else +  { +    TALER_LOG_DEBUG ("Running against the Pybank.\n"); +    if (GNUNET_OK != +        TALER_TESTING_prepare_bank (cfgfilename, +                                    "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; +    } +  } + +  ret = TALER_TESTING_setup (&run, +                             NULL, +                             cfgfilename, +                             NULL, +                             GNUNET_NO); +  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; +} + + +/* end of test_bank_api_twisted.c */ diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c new file mode 100644 index 00000000..84d5dc9b --- /dev/null +++ b/src/testing/test_exchange_api.c @@ -0,0 +1,832 @@ +/* +  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/test_exchange_api.c + * @brief testcase to test exchange's HTTP API interface + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" + +/** + * 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" + + +/** + * Exchange configuration data. + */ +static struct TALER_TESTING_ExchangeConfiguration ec; + +/** + * Bank configuration data. + */ +static struct TALER_TESTING_BankConfiguration bc; + + +/** + * 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_wirewatch (label, CONFIG_FILE) + +/** + * Execute the taler-exchange-aggregator command with + * our configuration file. + * + * @param label label to use for the command. + */ +#define CMD_EXEC_AGGREGATOR(label) \ +  TALER_TESTING_cmd_exec_aggregator (label, CONFIG_FILE) + +/** + * Run wire transfer of funds from some user's account to the + * exchange. + * + * @param label label to use for the command. + * @param amount amount to transfer, i.e. "EUR:1" + */ +#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \ +  TALER_TESTING_cmd_admin_add_incoming (label, amount, \ +                                        &bc.exchange_auth,                \ +                                        bc.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) +{ +  /** +   * 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[] = { +    /** +     * Move money to the exchange's bank account. +     */ +    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1", +                              "EUR:5.01"), +    TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-1", +                                                 "EUR:5.01", +                                                 bc.user42_payto, +                                                 bc.exchange_payto, +                                                 "create-reserve-1"), +    /** +     * Make a reserve exist, according to the previous +     * transfer. +     */ +    CMD_EXEC_WIREWATCH ("wirewatch-1"), +    /** +     * Withdraw EUR:5. +     */ +    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", +                                       "create-reserve-1", +                                       "EUR:5", +                                       MHD_HTTP_OK), +    /** +     * Check the reserve is depleted. +     */ +    TALER_TESTING_cmd_status ("status-1", +                              "create-reserve-1", +                              "EUR:0", +                              MHD_HTTP_OK), +    TALER_TESTING_cmd_end () +  }; + +  struct TALER_TESTING_Command spend[] = { +    /** +     * Spend the coin. +     */ +    TALER_TESTING_cmd_deposit ("deposit-simple", +                               "withdraw-coin-1", +                               0, +                               bc.user42_payto, +                               "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:5", +                               MHD_HTTP_OK), +    /** +     * Try to overdraw. +     */ +    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2", +                                       "create-reserve-1", +                                       "EUR:5", +                                       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, +                               "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:5", +                               MHD_HTTP_CONFLICT), +    /* Try to double spend using a different transaction id. +     * The test needs the contract terms to differ. This +     * is currently the case because of the "timestamp" field, +     * which is set automatically by #TALER_TESTING_cmd_deposit(). +     * This could theoretically fail if at some point a deposit +     * command executs in less than 1 ms. */// +    TALER_TESTING_cmd_deposit ("deposit-double-1", +                               "withdraw-coin-1", +                               0, +                               bc.user43_payto, +                               "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:5", +                               MHD_HTTP_CONFLICT), +    /** +     * Try to double spend with different proposal. +     */ +    TALER_TESTING_cmd_deposit ("deposit-double-2", +                               "withdraw-coin-1", +                               0, +                               bc.user43_payto, +                               "{\"items\":[{\"name\":\"ice cream\",\"value\":2}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:5", +                               MHD_HTTP_CONFLICT), +    TALER_TESTING_cmd_end () +  }; + +  struct TALER_TESTING_Command refresh[] = { +    /* Fill reserve with EUR:5, 1ct is for fees. */ +    CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve-1", +                              "EUR:5.01"), +    TALER_TESTING_cmd_check_bank_admin_transfer ("ck-refresh-create-reserve-1", +                                                 "EUR:5.01", +                                                 bc.user42_payto, +                                                 bc.exchange_payto, +                                                 "refresh-create-reserve-1"), +    /** +     * Make previous command effective. +     */ +    CMD_EXEC_WIREWATCH ("wirewatch-2"), +    /** +     * Withdraw EUR:5. +     */ +    TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1", +                                       "refresh-create-reserve-1", +                                       "EUR:5", +                                       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", +                               "refresh-withdraw-coin-1", +                               0, +                               bc.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_refresh_melt_double ("refresh-melt-1", +                                           "refresh-withdraw-coin-1", +                                           MHD_HTTP_OK, +                                           NULL), +    /** +     * Complete (successful) melt operation, and +     * withdraw the coins +     */ +    TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-1", +                                      "refresh-melt-1", +                                      MHD_HTTP_OK), +    /** +     * Do it again to check idempotency +     */ +    TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-1-idempotency", +                                      "refresh-melt-1", +                                      MHD_HTTP_OK), +    /** +     * Test that /refresh/link works +     */ +    TALER_TESTING_cmd_refresh_link ("refresh-link-1", +                                    "refresh-reveal-1", +                                    MHD_HTTP_OK), +    /** +     * Try to spend a refreshed EUR:1 coin +     */ +    TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-1a", +                               "refresh-reveal-1-idempotency", +                               0, +                               bc.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-1b", +                               "refresh-reveal-1", +                               3, +                               bc.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_refresh_melt ("refresh-melt-failing", +                                    "refresh-withdraw-coin-1", +                                    MHD_HTTP_CONFLICT, +                                    NULL), +    /* Test running a failing melt operation (on a coin that +       was itself revealed and subsequently deposited) */ +    TALER_TESTING_cmd_refresh_melt ("refresh-melt-failing-2", +                                    "refresh-reveal-1", +                                    MHD_HTTP_CONFLICT, +                                    NULL), + +    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, +                                         MHD_HTTP_ACCEPTED, +                                         NULL), +    /* Try resolving a deposit's WTID for a failed deposit. +     * As the deposit failed, the answer should be that the +     * exchange does NOT know about the deposit. +     */// +    TALER_TESTING_cmd_track_transaction ("deposit-wtid-failing", +                                         "deposit-double-2", +                                         0, +                                         MHD_HTTP_NOT_FOUND, +                                         NULL), +    /* Try resolving an undefined (all zeros) WTID; this +     * should fail as obviously the exchange didn't use that +     * WTID value for any transaction. +     */// +    TALER_TESTING_cmd_track_transfer_empty ("wire-deposit-failing", +                                            NULL, +                                            0, +                                            MHD_HTTP_NOT_FOUND), +    /* Run transfers. Note that _actual_ aggregation will NOT +     * happen here, as each deposit operation is run with a +     * fresh merchant public key, so the aggregator will treat +     * them as "different" merchants and do the wire transfers +     * individually. */// +    CMD_EXEC_AGGREGATOR ("run-aggregator"), +    /** +     * Check all the transfers took place. +     */ +    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-499c", +                                           ec.exchange_url, +                                           "EUR:4.98", +                                           bc.exchange_payto, +                                           bc.user42_payto), +    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c1", +                                           ec.exchange_url, +                                           "EUR:0.98", +                                           bc.exchange_payto, +                                           bc.user42_payto), +    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c2", +                                           ec.exchange_url, +                                           "EUR:0.98", +                                           bc.exchange_payto, +                                           bc.user42_payto), +    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c", +                                           ec.exchange_url, +                                           "EUR:0.08", +                                           bc.exchange_payto, +                                           bc.user43_payto), +    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"), +    TALER_TESTING_cmd_track_transaction ("deposit-wtid-ok", +                                         "deposit-simple", +                                         0, +                                         MHD_HTTP_OK, +                                         "check_bank_transfer-499c"), +    TALER_TESTING_cmd_track_transfer ("wire-deposit-success-bank", +                                      "check_bank_transfer-99c1", +                                      0, +                                      MHD_HTTP_OK, +                                      "EUR:0.98", +                                      "EUR:0.01"), +    TALER_TESTING_cmd_track_transfer ("wire-deposits-success-wtid", +                                      "deposit-wtid-ok", +                                      0, +                                      MHD_HTTP_OK, +                                      "EUR:4.98", +                                      "EUR:0.01"), +    TALER_TESTING_cmd_end () +  }; + + +  /** +   * This block checks whether a wire deadline +   * very far in the future does NOT get aggregated now. +   */ +  struct TALER_TESTING_Command unaggregation[] = { +    TALER_TESTING_cmd_check_bank_empty ("far-future-aggregation-a"), +    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-unaggregated", +                              "EUR:5.01"), +    /* "consume" reserve creation transfer.  */ +    TALER_TESTING_cmd_check_bank_admin_transfer ( +      "check-create-reserve-unaggregated", +      "EUR:5.01", +      bc.user42_payto, +      bc.exchange_payto, +      "create-reserve-unaggregated"), +    CMD_EXEC_WIREWATCH ("wirewatch-unaggregated"), +    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated", +                                       "create-reserve-unaggregated", +                                       "EUR:5", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit ("deposit-unaggregated", +                               "withdraw-coin-unaggregated", +                               0, +                               bc.user43_payto, +                               "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}", +                               GNUNET_TIME_relative_multiply ( +                                 GNUNET_TIME_UNIT_YEARS, +                                 3000), +                               "EUR:5", +                               MHD_HTTP_OK), +    CMD_EXEC_AGGREGATOR ("aggregation-attempt"), + +    TALER_TESTING_cmd_check_bank_empty +      ("far-future-aggregation-b"), + +    TALER_TESTING_cmd_end () +  }; + +  struct TALER_TESTING_Command refund[] = { +    /** +     * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per +     * config. +     */ +    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-r1", +                              "EUR:5.01"), +    TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-r1", +                                                 "EUR:5.01", +                                                 bc.user42_payto, +                                                 bc.exchange_payto, +                                                 "create-reserve-r1"), +    /** +     * Run wire-watch to trigger the reserve creation. +     */ +    CMD_EXEC_WIREWATCH ("wirewatch-3"), +    /* Withdraw a 5 EUR coin, at fee of 1 ct */ +    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1", +                                       "create-reserve-r1", +                                       "EUR:5", +                                       MHD_HTTP_OK), +    /** +     * Spend 5 EUR of the 5 EUR coin (in full) (merchant would +     * receive EUR:4.99 due to 1 ct deposit fee) +     */ +    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), +    /** +     * Run transfers. Should do nothing as refund deadline blocks it +     */ +    CMD_EXEC_AGGREGATOR ("run-aggregator-refund"), +    /* Check that aggregator didn't do anything, as expected. +     * 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. */// +    TALER_TESTING_cmd_check_bank_empty ("check_bank_transfer-pre-refund"), +    TALER_TESTING_cmd_refund ("refund-ok", +                              MHD_HTTP_OK, +                              "EUR:5", +                              "EUR:0.01", +                              "deposit-refund-1"), +    TALER_TESTING_cmd_refund ("refund-ok-double", +                              MHD_HTTP_OK, +                              "EUR:5", +                              "EUR:0.01", +                              "deposit-refund-1"), +    /* Previous /refund(s) had id == 0.  */ +    TALER_TESTING_cmd_refund_with_id ("refund-conflicting", +                                      MHD_HTTP_CONFLICT, +                                      "EUR:5", +                                      "EUR:0.01", +                                      "deposit-refund-1", +                                      1), +    /** +     * Spend 4.99 EUR of the refunded 4.99 EUR coin (1ct gone +     * due to refund) (merchant would receive EUR:4.98 due to +     * 1 ct deposit fee) */ +    TALER_TESTING_cmd_deposit ("deposit-refund-2", +                               "withdraw-coin-r1", +                               0, +                               bc.user42_payto, +                               "{\"items\":[{\"name\":\"more ice cream\",\"value\":\"EUR:5\"}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:4.99", +                               MHD_HTTP_OK), +    /** +     * Run transfers. This will do the transfer as refund deadline +     * was 0 +     */ +    CMD_EXEC_AGGREGATOR ("run-aggregator-3"), +    /** +     * Check that deposit did run. +     */ +    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-pre-refund", +                                           ec.exchange_url, +                                           "EUR:4.97", +                                           bc.exchange_payto, +                                           bc.user42_payto), +    /** +     * Run failing refund, as past deadline & aggregation. +     */ +    TALER_TESTING_cmd_refund ("refund-fail", +                              MHD_HTTP_GONE, +                              "EUR:4.99", +                              "EUR:0.01", +                              "deposit-refund-2"), +    TALER_TESTING_cmd_check_bank_empty ("check-empty-after-refund"), +    /** +     * Test refunded coins are never executed, even past +     * refund deadline +     */ +    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-rb", +                              "EUR:5.01"), +    TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-rb", +                                                 "EUR:5.01", +                                                 bc.user42_payto, +                                                 bc.exchange_payto, +                                                 "create-reserve-rb"), +    CMD_EXEC_WIREWATCH ("wirewatch-rb"), +    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-rb", +                                       "create-reserve-rb", +                                       "EUR:5", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit ("deposit-refund-1b", +                               "withdraw-coin-rb", +                               0, +                               bc.user42_payto, +                               "{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:5\"}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:5", +                               MHD_HTTP_OK), +    /** +     * Trigger refund (before aggregator had a chance to execute +     * deposit, even though refund deadline was zero). +     */ +    TALER_TESTING_cmd_refund ("refund-ok-fast", +                              MHD_HTTP_OK, +                              "EUR:5", +                              "EUR:0.01", +                              "deposit-refund-1b"), +    /** +     * Run transfers. This will do the transfer as refund deadline +     * was 0, except of course because the refund succeeded, the +     * transfer should no longer be done. +     */CMD_EXEC_AGGREGATOR ("run-aggregator-3b"), +    /* check that aggregator didn't do anything, as expected */ +    TALER_TESTING_cmd_check_bank_empty ("check-refund-fast-not-run"), +    TALER_TESTING_cmd_end () +  }; + +  struct TALER_TESTING_Command recoup[] = { +    /** +     * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per +     * config. +     */ +    CMD_TRANSFER_TO_EXCHANGE ("recoup-create-reserve-1", +                              "EUR:5.01"), +    TALER_TESTING_cmd_check_bank_admin_transfer ("recoup-create-reserve-1", +                                                 "EUR:5.01", +                                                 bc.user42_payto, +                                                 bc.exchange_payto, +                                                 "recoup-create-reserve-1"), +    /** +     * Run wire-watch to trigger the reserve creation. +     */ +    CMD_EXEC_WIREWATCH ("wirewatch-4"), +    /* Withdraw a 5 EUR coin, at fee of 1 ct */ +    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1", +                                       "recoup-create-reserve-1", +                                       "EUR:5", +                                       MHD_HTTP_OK), +    /* Make coin invalid */ +    TALER_TESTING_cmd_revoke ("revoke-0-EUR:5", +                              MHD_HTTP_OK, +                              "recoup-withdraw-coin-1", +                              CONFIG_FILE), +    /* Refund coin to bank account */ +    TALER_TESTING_cmd_recoup ("recoup-1", +                              MHD_HTTP_OK, +                              "recoup-withdraw-coin-1", +                              "EUR:5", +                              NULL), +    /* Check the money is back with the reserve */ +    TALER_TESTING_cmd_status ("recoup-reserve-status-1", +                              "recoup-create-reserve-1", +                              "EUR:5.0", +                              MHD_HTTP_OK), +    /* Re-withdraw from this reserve */ +    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2", +                                       "recoup-create-reserve-1", +                                       "EUR:1", +                                       MHD_HTTP_OK), +    /** +     * This withdrawal will test the logic to create a "recoup" +     * element to insert into the reserve's history. +     */ +    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2-over", +                                       "recoup-create-reserve-1", +                                       "EUR:10", +                                       MHD_HTTP_CONFLICT), +    TALER_TESTING_cmd_status ("recoup-reserve-status-2", +                              "recoup-create-reserve-1", +                              "EUR:3.99", +                              MHD_HTTP_OK), + +    /* These commands should close the reserve because +     * the aggregator is given a config file that ovverrides +     * the reserve expiration time (making it now-ish) */ +    CMD_TRANSFER_TO_EXCHANGE ("short-lived-reserve", +                              "EUR:5.01"), +    TALER_TESTING_cmd_check_bank_admin_transfer ("check-short-lived-reserve", +                                                 "EUR:5.01", +                                                 bc.user42_payto, +                                                 bc.exchange_payto, +                                                 "short-lived-reserve"), +    TALER_TESTING_cmd_exec_wirewatch ("short-lived-aggregation", +                                      CONFIG_FILE_EXPIRE_RESERVE_NOW), + +    TALER_TESTING_cmd_exec_aggregator ("close-reserves", +                                       CONFIG_FILE_EXPIRE_RESERVE_NOW), + +    TALER_TESTING_cmd_status ("short-lived-status", +                              "short-lived-reserve", +                              "EUR:0", +                              MHD_HTTP_OK), +    TALER_TESTING_cmd_withdraw_amount ("expired-withdraw", +                                       "short-lived-reserve", +                                       "EUR:1", +                                       MHD_HTTP_CONFLICT), +    TALER_TESTING_cmd_check_bank_transfer ("check_bank_short-lived_reimburse", +                                           ec.exchange_url, +                                           "EUR:5", +                                           bc.exchange_payto, +                                           bc.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... */// +    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, +                                                 "recoup-create-reserve-2"), +    /* Make previous command effective. */ +    CMD_EXEC_WIREWATCH ("wirewatch-5"), +    /* Withdraw a 1 EUR coin, at fee of 1 ct */ +    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2a", +                                       "recoup-create-reserve-2", +                                       "EUR:1", +                                       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", +                                       MHD_HTTP_OK), +    TALER_TESTING_cmd_deposit ("recoup-deposit-partial", +                               "recoup-withdraw-coin-2a", +                               0, +                               bc.user42_payto, +                               "{\"items\":[{\"name\":\"more ice cream\",\"value\":1}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:0.5", +                               MHD_HTTP_OK), +    TALER_TESTING_cmd_revoke ("revoke-1-EUR:1", +                              MHD_HTTP_OK, +                              "recoup-withdraw-coin-2a", +                              CONFIG_FILE), +    TALER_TESTING_cmd_recoup ("recoup-2", +                              MHD_HTTP_OK, +                              "recoup-withdraw-coin-2a", +                              "EUR:0.5", +                              NULL), +    TALER_TESTING_cmd_recoup ("recoup-2b", +                              MHD_HTTP_CONFLICT, +                              "recoup-withdraw-coin-2a", +                              "EUR:0.5", +                              NULL), +    TALER_TESTING_cmd_deposit ("recoup-deposit-revoked", +                               "recoup-withdraw-coin-2b", +                               0, +                               bc.user42_payto, +                               "{\"items\":[{\"name\":\"more ice cream\",\"value\":1}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:1", +                               MHD_HTTP_NOT_FOUND), +    /* Test deposit fails after recoup, with proof in recoup */ + +    /* Note that, the exchange will never return the coin's transaction +     * history with recoup data, as we get a 404 on the DK! */ +    TALER_TESTING_cmd_deposit ("recoup-deposit-partial-after-recoup", +                               "recoup-withdraw-coin-2a", +                               0, +                               bc.user42_payto, +                               "{\"items\":[{\"name\":\"extra ice cream\",\"value\":1}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:0.5", +                               MHD_HTTP_NOT_FOUND), +    /* Test that revoked coins cannot be withdrawn */ +    CMD_TRANSFER_TO_EXCHANGE ("recoup-create-reserve-3", +                              "EUR:1.01"), +    TALER_TESTING_cmd_check_bank_admin_transfer ( +      "check-recoup-create-reserve-3", +      "EUR:1.01", +      bc.user42_payto, +      bc.exchange_payto, +      "recoup-create-reserve-3"), +    CMD_EXEC_WIREWATCH ("wirewatch-6"), +    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-3-revoked", +                                       "recoup-create-reserve-3", +                                       "EUR:1", +                                       MHD_HTTP_NOT_FOUND), +    /* check that we are empty before the rejection test */ +    TALER_TESTING_cmd_check_bank_empty ("check-empty-again"), + +    TALER_TESTING_cmd_end () +  }; + +#define RESERVE_OPEN_CLOSE_CHUNK 4 +#define RESERVE_OPEN_CLOSE_ITERATIONS 3 + +  struct TALER_TESTING_Command reserve_open_close[(RESERVE_OPEN_CLOSE_ITERATIONS +                                                   * RESERVE_OPEN_CLOSE_CHUNK) +                                                  + 1]; +  for (unsigned int i = 0; +       i < RESERVE_OPEN_CLOSE_ITERATIONS; +       i++) +  { +    reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 0] +      = 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); +    reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 2] +      = TALER_TESTING_cmd_exec_aggregator ("reserve-open-close-aggregation", +                                           CONFIG_FILE_EXPIRE_RESERVE_NOW); +    reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 3] +      = TALER_TESTING_cmd_status ("reserve-open-close-status", +                                  "reserve-open-close-key", +                                  "EUR:0", +                                  MHD_HTTP_OK); +  } +  reserve_open_close[RESERVE_OPEN_CLOSE_ITERATIONS * RESERVE_OPEN_CLOSE_CHUNK] +    = TALER_TESTING_cmd_end (); + +  { +    struct TALER_TESTING_Command commands[] = { +      TALER_TESTING_cmd_batch ("wire", +                               wire), +      TALER_TESTING_cmd_batch ("withdraw", +                               withdraw), +      TALER_TESTING_cmd_batch ("spend", +                               spend), +      TALER_TESTING_cmd_batch ("refresh", +                               refresh), +      TALER_TESTING_cmd_batch ("track", +                               track), +      TALER_TESTING_cmd_batch ("unaggregation", +                               unaggregation), +      TALER_TESTING_cmd_batch ("refund", +                               refund), +      TALER_TESTING_cmd_batch ("recoup", +                               recoup), +      TALER_TESTING_cmd_batch ("reserve-open-close", +                               reserve_open_close), +      /* End the suite. */ +      TALER_TESTING_cmd_end () +    }; + +    TALER_TESTING_run_with_fakebank (is, +                                     commands, +                                     bc.exchange_auth.wire_gateway_url); +  } +} + + +int +main (int argc, +      char *const *argv) +{ +  /* These environment variables get in the way... */ +  unsetenv ("XDG_DATA_HOME"); +  unsetenv ("XDG_CONFIG_HOME"); +  GNUNET_log_setup ("test-exchange-api", +                    "INFO", +                    NULL); +  /* Check fakebank port is available and get config */ +  if (GNUNET_OK != +      TALER_TESTING_prepare_fakebank (CONFIG_FILE, +                                      "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, +                                          &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; +} + + +/* end of test_exchange_api.c */ diff --git a/src/testing/test_exchange_api.conf b/src/testing/test_exchange_api.conf new file mode 100644 index 00000000..291e0a0e --- /dev/null +++ b/src/testing/test_exchange_api.conf @@ -0,0 +1,210 @@ +# This file is in the public domain. +# + +[PATHS] +# Persistant data storage for the testcase +TALER_TEST_HOME = test_exchange_api_home/ + + +[taler] +# Currency supported by the exchange (can only be one) +CURRENCY = EUR + +[auditor] +BASE_URL = "http://localhost:8083/" + +# HTTP port the auditor listens to +PORT = 8083 + + +[exchange] +# how long is one signkey valid? +signkey_duration = 4 weeks + +# how long are the signatures with the signkey valid? +legal_duration = 2 years + +# how long do we provide to clients denomination and signing keys +# ahead of time? +lookahead_provide = 4 weeks 1 day + +# HTTP port the exchange listens to +PORT = 8081 + +# Master public key used to sign the exchange's various keys +MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG + +# How to access our database +DB = postgres + +# Base URL of the exchange. Must be set to a URL where the +# exchange (or the twister) is actually listening. +BASE_URL = "http://localhost:8081/" + +# Keep it short so the test runs fast. +LOOKAHEAD_SIGN = 12 h + + +[exchangedb-postgres] +CONFIG = "postgres:///talercheck" + + +[auditordb-postgres] +CONFIG = "postgres:///talercheck" + +# Sections starting with "account-" configure the bank accounts +# of the exchange.  The "URL" specifies the account in +# payto://-format, while the WIRE_JSON specifies the +# (possibly offline) signed version to be returned in /wire. +# WIRE_JSON is optional, as not all accounts must be +# advertised in /wire. +[account-1] +# What is the URL of our account? +URL = "payto://x-taler-bank/localhost/42" +# This is the response we give out for the /wire request.  It provides +# wallets with the bank information for transfers to the exchange. +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json +# Which wire plugin should we used to access the account? +METHOD = x-taler-bank + +WIRE_GATEWAY_URL = "http://localhost:9081/42/" + +# ENABLE_CREDIT = YES + + +[account-2] +# What is the bank account (with the "Taler Bank" demo system)? +URL = "payto://x-taler-bank/localhost/2" + +# This is the response we give out for the /wire request.  It provides +# wallets with the bank information for transfers to the exchange. +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-2.json + +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = Exchange +PASSWORD = x + +METHOD = x-taler-bank + +WIRE_GATEWAY_URL = "http://localhost:9081/2/" + +ENABLE_DEBIT = YES + +ENABLE_CREDIT = YES + +[bank] +HTTP_PORT = 9081 + +# Sections starting with "fee-" configure the wire fee for the +# respective wire method. +[fees-iban] +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 + +[fees-x-taler-bank] +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 + +# 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_overlap = 5 minutes +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_overlap = 5 minutes +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_overlap = 5 minutes +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_overlap = 5 minutes +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_overlap = 5 minutes +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_api_expire_reserve_now.conf b/src/testing/test_exchange_api_expire_reserve_now.conf new file mode 100644 index 00000000..05bca956 --- /dev/null +++ b/src/testing/test_exchange_api_expire_reserve_now.conf @@ -0,0 +1,4 @@ +@INLINE@ test_exchange_api.conf + +[exchangedb] +IDLE_RESERVE_EXPIRATION_TIME = 0 s diff --git a/src/testing/test_exchange_api_home/.config/taler/account-1.json b/src/testing/test_exchange_api_home/.config/taler/account-1.json new file mode 100644 index 00000000..48093f2a --- /dev/null +++ b/src/testing/test_exchange_api_home/.config/taler/account-1.json @@ -0,0 +1,5 @@ +{ +  "url": "payto://sepa/CH9300762011623852957", +  "salt": "N83T9J9202WCC8TQFDMJDWEGZNBEKA33C1ZM241VNYH88RZNTHPW509Y1M2YF7Y098R8VRESWQ05H03BK1SPAZCWE54KARDCKT5N8AG", +  "master_sig": "D4V5GJ998YK7D6N0N56AD0J6MZNFEW6MRZT2CFPVQ5ME3NMQ59AA2007CXYESSFGRN70CNCFM06858QSSENCWTZM8VHEJ93YQ20ZJ1R" +}
\ No newline at end of file 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 new file mode 100644 index 00000000..f39677ef --- /dev/null +++ b/src/testing/test_exchange_api_home/.config/taler/account-2.json @@ -0,0 +1,4 @@ +{ +  "url": "payto://x-taler-bank/localhost/2", +  "master_sig": "HEWC1XDS0QZ53YQR451VRKD4N968NXWGZXS30HJ59MJ0PESACK1ZYPYCAT15P08WD58C7D7F6EVN26D59JKA75XEBDQCM8VYFETK82R" +}
\ No newline at end of file diff --git a/src/testing/test_exchange_api_home/.config/taler/sepa.json b/src/testing/test_exchange_api_home/.config/taler/sepa.json new file mode 100644 index 00000000..b435ce86 --- /dev/null +++ b/src/testing/test_exchange_api_home/.config/taler/sepa.json @@ -0,0 +1,9 @@ +{ +  "name": "Max Musterman", +  "bic": "COBADEFF370", +  "type": "sepa", +  "sig": "4EVRC2MCJPXQC8MC00831DNWEXMZAP4JQDDE1A7R6KR3MANG24RC1VQ55AX5A2E35S58VW1VSTENFTPHG5MWG9BSN8B8WXSV21KKW20", +  "address": "Musterstadt", +  "salt": "3KTM1ZRMWGEQPQ254S4R5R4Q8XM0ZYWTCTE01TZ76MVBSQ6RX7A5DR08WXVH1DCHR1R7ACRB7X0EVC2XDW1CBZM9WFSD9TRMZ90BR98", +  "iban": "DE89370400440532013000" +}
\ No newline at end of file diff --git a/src/testing/test_exchange_api_home/.config/taler/test.json b/src/testing/test_exchange_api_home/.config/taler/test.json new file mode 100644 index 00000000..eca39424 --- /dev/null +++ b/src/testing/test_exchange_api_home/.config/taler/test.json @@ -0,0 +1,8 @@ +{ +  "salt": "AZPRFVJ58NM6M7J5CZQPJAH3EW5DYM52AEZ9Y1C1ER3W94QV8D8TQKF6CK8MYQRA9QMSKDQTGZ306ZS9GQ0M6R01CJ20KPP49WFDZK8", +  "name": "The exchange", +  "account_number": 3, +  "bank_url": "http://localhost:8082/", +  "type": "test", +  "sig": "RPQXP9S4P8PQP7HEZQNRSZCT0ATNEP8GW0P5TPM34V5RX86FCD670V44R9NETSYDDKB8SZV7TKY9PAJYTY51D3VDWY9XXQ5BPFRXR28" +} diff --git a/src/testing/test_exchange_api_home/.config/taler/x-taler-bank.json b/src/testing/test_exchange_api_home/.config/taler/x-taler-bank.json new file mode 100644 index 00000000..a6dc167e --- /dev/null +++ b/src/testing/test_exchange_api_home/.config/taler/x-taler-bank.json @@ -0,0 +1,5 @@ +{ +  "url": "payto://x-taler-bank/http://localhost:8082/2", +  "master_sig": "KQ0BWSCNVR7HGGSAMCYK8ZM30RBS1MHMXT3QBN01PZWC9TV72FEE5RJ7T84C8134EPV6WEBXXY2MTFNE8ZXST6JEJQKR8HX6FQPVY10", +  "master_pub": "98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG" +} 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 new file mode 100644 index 00000000..39492693 --- /dev/null +++ b/src/testing/test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv @@ -0,0 +1 @@ +pÚ^ó-Ú33ˆ€XXÁ!ˆ\0qúýµmUþ_‰ˆ
\ No newline at end of file diff --git a/src/testing/test_exchange_api_interpreter_on-off.c b/src/testing/test_exchange_api_interpreter_on-off.c new file mode 100644 index 00000000..e0ef7509 --- /dev/null +++ b/src/testing/test_exchange_api_interpreter_on-off.c @@ -0,0 +1,128 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/test_exchange_api_keys_cherry_picking_new.c + * @brief testcase to test exchange's /keys cherry picking ability + * @author Marcello Stanisci + * @author Christian Grothoff + */ + +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" + +/** + * XXX: + * + * This test helps in finding a way to use/modify the "normal" + * cert_cb to handle reconnections from serialized states as well. + * + * 1st step: simply turn the interpreter off and on again. + * 2nd step: turn the interpreter off and give a serial state + *           to reconnect. + */ + +/** + * Configuration file we use.  One (big) configuration is used + * for the various components for this test. + */ +#define CONFIG_FILE "test_exchange_api_keys_cherry_picking.conf" + +/** + * Exchange base URL; mainly purpose is to make the compiler happy. + */ +static char *exchange_url; + +/** + * Auditor base URL; mainly purpose is to make the compiler happy. + */ +static char *auditor_url; + + +/** + * Main function that will tell the interpreter what commands to + * run. + * + * @param cls closure + */ +static void +run (void *cls, +     struct TALER_TESTING_Interpreter *is) +{ + +  struct TALER_TESTING_Command commands[] = { + +    TALER_TESTING_cmd_end () +  }; + +  TALER_TESTING_run (is, +                     commands); +} + + +int +main (int argc, +      char *const *argv) +{ +  /* These environment variables get in the way... */ +  unsetenv ("XDG_DATA_HOME"); +  unsetenv ("XDG_CONFIG_HOME"); +  GNUNET_log_setup ("test-exchange-api-cherry-picking-new", +                    "DEBUG", NULL); +  TALER_TESTING_cleanup_files (CONFIG_FILE); +  /* @helpers.  Run keyup, create tables, ... Note: it +   * fetches the port number from config in order to see +   * if it's available. */ +  switch (TALER_TESTING_prepare_exchange (CONFIG_FILE, +                                          &auditor_url, +                                          &exchange_url)) +  { +  case GNUNET_SYSERR: +    GNUNET_break (0); +    return 1; +  case GNUNET_NO: +    return 77; +  case GNUNET_OK: +    if (GNUNET_OK != +        /* Set up event loop and reschedule context, plus +         * start/stop the exchange.  It calls TALER_TESTING_setup +         * which creates the 'is' object. +         */ +        TALER_TESTING_setup_with_exchange (&run, +                                           NULL, +                                           CONFIG_FILE)) +      return 1; +    break; +  default: +    GNUNET_break (0); +    return 1; +  } +  return 0; +} + + +/* end of test_exchange_api_keys_cherry_picking_new.c */ diff --git a/src/testing/test_exchange_api_keys_cherry_picking.c b/src/testing/test_exchange_api_keys_cherry_picking.c new file mode 100644 index 00000000..a104b805 --- /dev/null +++ b/src/testing/test_exchange_api_keys_cherry_picking.c @@ -0,0 +1,263 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/test_exchange_api_keys_cherry_picking.c + * @brief testcase to test exchange's /keys cherry picking ability + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" + + +/** + * 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" + +/** + * Used to increase the number of denomination keys. + */ +#define CONFIG_FILE_EXTENDED \ +  "test_exchange_api_keys_cherry_picking_extended.conf" + +/** + * Used to increase the number of denomination keys. + */ +#define CONFIG_FILE_EXTENDED_2 \ +  "test_exchange_api_keys_cherry_picking_extended_2.conf" + + +#define NDKS_RIGHT_BEFORE_SERIALIZATION 46 + +/** + * Add seconds. + * + * @param base absolute time to add seconds to. + * @param relative number of seconds to add. + * @return a new absolute time, modified according to @e relative. + */ +#define ADDSECS(base, secs) \ +  GNUNET_TIME_absolute_add \ +    (base, \ +    GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, \ +                                   secs)) + +/** + * Subtract seconds. + * + * @param base absolute time to subtract seconds to. + * @param secs relative number of _seconds_ to subtract. + * @return a new absolute time, modified according to @e relative. + */ +#define SUBSECS(base, secs) \ +  GNUNET_TIME_absolute_subtract \ +    (base, \ +    GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, \ +                                   secs)) +#define JAN1971 "1971-01-01" +#define JAN2030 "2030-01-01" + +/** + * Exchange configuration data. + */ +static struct TALER_TESTING_ExchangeConfiguration ec; + + +/** + * Wrapper around the time parser. + * + * @param str human-readable time string. + * @return the parsed time from @a str. + */ +static struct GNUNET_TIME_Absolute +TTH_parse_time (const char *str) +{ +  struct GNUNET_TIME_Absolute ret; + +  GNUNET_assert +    (GNUNET_OK == GNUNET_STRINGS_fancy_time_to_absolute (str, +                                                         &ret)); +  return ret; +} + + +/** + * Main function that will tell the interpreter what commands to + * run. + * + * @param cls closure + * @param is[in,out] interpreter state + */ +static void +run (void *cls, +     struct TALER_TESTING_Interpreter *is) +{ +  struct TALER_TESTING_Command keys_serialization[] = { +    TALER_TESTING_cmd_serialize_keys +      ("serialize-keys"), +    TALER_TESTING_cmd_connect_with_state +      ("reconnect-with-state", +      "serialize-keys"), +    /** +     * Make sure we have the same keys situation as +     * it was before the serialization. +     */ +    TALER_TESTING_cmd_check_keys_with_now +      ("check-keys-after-deserialization", +      4, +      NDKS_RIGHT_BEFORE_SERIALIZATION, +      /** +       * Pretend 5 seconds passed. +       */ +      ADDSECS (TTH_parse_time (JAN2030), +               5)), +    /** +     * Use one of the deserialized keys. +     */ +    TALER_TESTING_cmd_wire +      ("verify-/wire-with-serialized-keys", +      "x-taler-bank", +      NULL, +      MHD_HTTP_OK), +    TALER_TESTING_cmd_end (), +  }; + +  struct TALER_TESTING_Command ordinary_cherry_pick[] = { +    /** +     * 1 DK with 80s withdraw duration, lookahead_sign is 60s +     * => expect 1 DK. +     */ +    TALER_TESTING_cmd_check_keys ("check-keys-1", +                                  1, /* generation */ +                                  1), +    /** +     * The far-future now will cause "keyup" to start a fresh +     * key set.  The new KS will have only one key, because the +     * current lookahead_sign == 60 seconds and the key's withdraw +     * duration is 80 seconds. +     */TALER_TESTING_cmd_exec_keyup_with_now +      ("keyup-1", +      CONFIG_FILE, +      TTH_parse_time (JAN2030)), +    /** +    * Should return 1 new key, + the original one.  NOTE: the +    * original DK will never be 'cancelled' as for the current +    * libtalerexchange logic, so it must always be counted. +    */TALER_TESTING_cmd_check_keys_with_now +      ("check-keys-2", +      2,  /* generation */ +      2, +      TTH_parse_time (JAN2030)), +    TALER_TESTING_cmd_exec_keyup_with_now +      ("keyup-3", +      CONFIG_FILE_EXTENDED_2, +      /* Taking care of not using a 'now' that equals the +       * last DK timestamp, otherwise it would get silently +       * overridden.  */ +      ADDSECS (TTH_parse_time (JAN2030), +               10)), + +    /** +     * Expected number of DK: +     * +     * 3500 (the lookahead_sign time frame, in seconds) +     * - 69 (how many seconds are covered by the latest DK) +     * ---- +     * 3431 +     * / 79 (how many seconds each DK will cover) +     * ---- +     *   44 (rounded up) +     *  + 2 (old DKs already stored locally: 1 from the +     *       very initial setup, and 1 from the 'keyup-1' CMD) +     * ---- +     *   46 +     */TALER_TESTING_cmd_check_keys_with_now +      ("check-keys-3", +      3, +      NDKS_RIGHT_BEFORE_SERIALIZATION, +      TTH_parse_time (JAN2030)), + +    TALER_TESTING_cmd_end () +  }; +  struct TALER_TESTING_Command commands[] = { + +    TALER_TESTING_cmd_batch ("ordinary-cherry-pick", +                             ordinary_cherry_pick), +    TALER_TESTING_cmd_batch ("keys-serialization", +                             keys_serialization), +    TALER_TESTING_cmd_end () +  }; + +  TALER_TESTING_run (is, +                     commands); +} + + +int +main (int argc, +      char *const *argv) +{ +  /* These environment variables get in the way... */ +  unsetenv ("XDG_DATA_HOME"); +  unsetenv ("XDG_CONFIG_HOME"); +  GNUNET_log_setup ("test-exchange-api-cherry-picking", +                    "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, +                                          &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; +} + + +/* end of test_exchange_api_keys_cherry_picking.c */ diff --git a/src/testing/test_exchange_api_keys_cherry_picking.conf b/src/testing/test_exchange_api_keys_cherry_picking.conf new file mode 100644 index 00000000..c5a69df3 --- /dev/null +++ b/src/testing/test_exchange_api_keys_cherry_picking.conf @@ -0,0 +1,184 @@ +# This file is in the public domain. +# +[PATHS] +# Persistent data storage for the testcase +TALER_TEST_HOME = test_exchange_api_keys_cherry_picking_home/ + +# Persistant 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 + +[auditor] +BASE_URL = "http://localhost:8083/" + +# HTTP port the auditor listens to +PORT = 8083 + +[exchange] + +KEYDIR = ${TALER_TEST_HOME}/.local/share/taler/exchange/live-keys/ + +# how long is one signkey valid? +signkey_duration = 5 seconds + +# how long are the signatures with the signkey valid? +legal_duration = 2 years + +# This vaule causes keys to be *RETURNED* in a /keys response. +# It's a relative time that materializes always in now+itsvalue. +# We keep it very high, so as to not introduce divergencies between +# keys that have been created and keys that are returned along /keys. +lookahead_provide = 10000 seconds + +# This value causes keys to be *CREATED*.  The rule is that +# at any given time there are always N keys whose all the withdraw +# durations sum up to a time window as big as lookahead_sign. +lookahead_sign = 60 s + +# HTTP port the exchange listens to +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" + + +[account-1] +# This is the response we give out for the /wire request.  It provides +# wallets with the bank information for transfers to the exchange. +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/iban.json + +# What is the URL of our bank account? Must match WIRE_RESPONSE above! +URL = payto://x-taler-bank/localhost/42 + +METHOD = "x-taler-bank" + +WIRE_GATEWAY_URL = "http://localhost:9082/42/" + + +[account-2] +# This is the response we give out for the /wire request.  It provides +# wallets with the bank information for transfers to the exchange. +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/x-taler-bank.json + +# What is the URL of our bank account? Must match WIRE_RESPONSE above! +URL = payto://x-taler-bank/localhost/2 + +WIRE_GATEWAY_URL = "http://localhost:9082/2/" + +METHOD = "x-taler-bank" + +# Authentication information for basic authentication +TALER_BANK_AUTH_METHOD = "basic" +USERNAME = user +PASSWORD = pass + +ENABLE_DEBIT = YES + +ENABLE_CREDIT = YES + +[bank] +HTTP_PORT=8082 + +[fees-x-taler-bank] +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2017 = EUR:0.01 +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 +WIRE-FEE-2028 = EUR:0.01 +WIRE-FEE-2029 = EUR:0.01 +WIRE-FEE-2030 = EUR:0.01 +WIRE-FEE-2031 = EUR:0.01 +WIRE-FEE-2032 = EUR:0.01 +WIRE-FEE-2033 = EUR:0.01 +WIRE-FEE-2034 = EUR:0.01 +WIRE-FEE-2035 = EUR:0.01 + +CLOSING-FEE-2017 = EUR:0.01 +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 +CLOSING-FEE-2028 = EUR:0.01 +CLOSING-FEE-2029 = EUR:0.01 +CLOSING-FEE-2030 = EUR:0.01 +CLOSING-FEE-2031 = EUR:0.01 +CLOSING-FEE-2032 = EUR:0.01 +CLOSING-FEE-2033 = EUR:0.01 +CLOSING-FEE-2034 = EUR:0.01 +CLOSING-FEE-2035 = EUR:0.01 + + +[fees-iban] +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2017 = EUR:0.01 +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 + +CLOSING-FEE-2017 = EUR:0.01 +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 + +[coin_eur_1] +value = EUR:1 +duration_overlap = 1 s +duration_withdraw = 80 s +duration_spend = 80 s +duration_legal = 60 s +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +rsa_keysize = 1024 diff --git a/src/testing/test_exchange_api_keys_cherry_picking_extended.conf b/src/testing/test_exchange_api_keys_cherry_picking_extended.conf new file mode 100644 index 00000000..c49f1edd --- /dev/null +++ b/src/testing/test_exchange_api_keys_cherry_picking_extended.conf @@ -0,0 +1,5 @@ +@INLINE@ test_exchange_api_keys_cherry_picking.conf + +[exchange] +# Lengthen over original value (60 s) +LOOKAHEAD_SIGN = 90 s diff --git a/src/testing/test_exchange_api_keys_cherry_picking_extended_2.conf b/src/testing/test_exchange_api_keys_cherry_picking_extended_2.conf new file mode 100644 index 00000000..8097a6cd --- /dev/null +++ b/src/testing/test_exchange_api_keys_cherry_picking_extended_2.conf @@ -0,0 +1,5 @@ +@INLINE@ test_exchange_api_keys_cherry_picking_extended.conf + +[exchange] +# Lengthen over firstly extended value (100 s) +LOOKAHEAD_SIGN = 3500 s diff --git a/src/testing/test_exchange_api_keys_cherry_picking_home/.config/taler/x-taler-bank.json b/src/testing/test_exchange_api_keys_cherry_picking_home/.config/taler/x-taler-bank.json new file mode 100644 index 00000000..f39677ef --- /dev/null +++ b/src/testing/test_exchange_api_keys_cherry_picking_home/.config/taler/x-taler-bank.json @@ -0,0 +1,4 @@ +{ +  "url": "payto://x-taler-bank/localhost/2", +  "master_sig": "HEWC1XDS0QZ53YQR451VRKD4N968NXWGZXS30HJ59MJ0PESACK1ZYPYCAT15P08WD58C7D7F6EVN26D59JKA75XEBDQCM8VYFETK82R" +}
\ No newline at end of file diff --git a/src/testing/test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/offline-keys/master.priv b/src/testing/test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/offline-keys/master.priv new file mode 100644 index 00000000..39492693 --- /dev/null +++ b/src/testing/test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/offline-keys/master.priv @@ -0,0 +1 @@ +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 new file mode 100644 index 00000000..f63d5da7 --- /dev/null +++ b/src/testing/test_exchange_api_overlapping_keys_bug.c @@ -0,0 +1,130 @@ +/* +  This file is part of TALER +  Copyright (C) 2018, 2019 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/test_exchange_api_overlapping_keys_bug.c + * @brief testcase to test exchange's /keys cherry picking ability and + *          other /keys related operations + * @author Marcello Stanisci + * @author Christian Grothoff + */ + +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" + +/** + * 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" + +/** + * Used to increase the number of denomination keys. + */ +#define CONFIG_FILE_EXTENDED \ +  "test_exchange_api_keys_cherry_picking_extended.conf" + +/** + * Used to increase the number of denomination keys. + */ +#define CONFIG_FILE_EXTENDED_2 \ +  "test_exchange_api_keys_cherry_picking_extended_2.conf" + +/** + * Exchange configuration data. + */ +static struct TALER_TESTING_ExchangeConfiguration ec; + + +/** + * Main function that will tell the interpreter what commands to + * run. + * + * @param cls closure + */ +static void +run (void *cls, +     struct TALER_TESTING_Interpreter *is) +{ +  struct TALER_TESTING_Command commands[] = { +    TALER_TESTING_cmd_check_keys ("first-download", +                                  1, +                                  1), +    /* Causes GET /keys?last_denom_issue=0 */ +    TALER_TESTING_cmd_check_keys_with_last_denom ("second-download", +                                                  3, +                                                  1, +                                                  GNUNET_TIME_UNIT_ZERO_ABS), +    TALER_TESTING_cmd_end () +  }; + +  TALER_TESTING_run (is, +                     commands); +} + + +int +main (int argc, +      char *const *argv) +{ +  /* These environment variables get in the way... */ +  unsetenv ("XDG_DATA_HOME"); +  unsetenv ("XDG_CONFIG_HOME"); +  GNUNET_log_setup ("test-exchange-api-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, +                                          &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; +} + + +/* end of test_exchange_api_overlapping_keys_bug.c */ diff --git a/src/testing/test_exchange_api_revocation.c b/src/testing/test_exchange_api_revocation.c new file mode 100644 index 00000000..110dec16 --- /dev/null +++ b/src/testing/test_exchange_api_revocation.c @@ -0,0 +1,231 @@ +/* +  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/test_exchange_api_revocation.c + * @brief testcase to test key revocation handling via the exchange's HTTP API interface + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" + +/** + * Configuration file we use.  One (big) configuration is used + * for the various components for this test. + */ +#define CONFIG_FILE "test_exchange_api.conf" + +/** + * Exchange configuration data. + */ +static struct TALER_TESTING_ExchangeConfiguration ec; + +/** + * Bank configuration data. + */ +static struct TALER_TESTING_BankConfiguration bc; + + +/** + * Main function that will tell the interpreter what commands to + * run. + * + * @param cls closure + */ +static void +run (void *cls, +     struct TALER_TESTING_Interpreter *is) +{ +  struct TALER_TESTING_Command revocation[] = { +    /** +     * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per +     * config. +     */ +    TALER_TESTING_cmd_admin_add_incoming ("create-reserve-1", +                                          "EUR:5.01", +                                          &bc.exchange_auth, +                                          bc.user42_payto), +    TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-1", +                                                 "EUR:5.01", +                                                 bc.user42_payto, +                                                 bc.exchange_payto, +                                                 "create-reserve-1"), +    /** +     * Run wire-watch to trigger the reserve creation. +     */ +    TALER_TESTING_cmd_exec_wirewatch ("wirewatch-4", +                                      CONFIG_FILE), +    /* Withdraw a 5 EUR coin, at fee of 1 ct */ +    TALER_TESTING_cmd_withdraw_amount ("withdraw-revocation-coin-1", +                                       "create-reserve-1", +                                       "EUR:5", +                                       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, +                               "{\"items\":[{\"name\":\"ice cream\",\"value\":\"EUR:1\"}]}", +                               GNUNET_TIME_UNIT_ZERO, +                               "EUR:1", +                               MHD_HTTP_OK), +    /** +     * Melt SOME of the rest of the coin's value +     * (EUR:3.17 = 3x EUR:1.03 + 7x EUR:0.13) */ +    TALER_TESTING_cmd_refresh_melt ("refresh-melt-1", +                                    "withdraw-revocation-coin-1", +                                    MHD_HTTP_OK, +                                    NULL), +    /** +     * Complete (successful) melt operation, and withdraw the coins +     */ +    TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-1", +                                      "refresh-melt-1", +                                      MHD_HTTP_OK), +    /* Make refreshed coin invalid */ +    TALER_TESTING_cmd_revoke ("revoke-2-EUR:5", +                              MHD_HTTP_OK, +                              "refresh-melt-1", +                              CONFIG_FILE), +    /* Refund coin to original coin */ +    TALER_TESTING_cmd_recoup ("recoup-1a", +                              MHD_HTTP_OK, +                              "refresh-reveal-1#0", +                              "EUR:1", +                              "refresh-melt-1"), +    TALER_TESTING_cmd_recoup ("recoup-1b", +                              MHD_HTTP_OK, +                              "refresh-reveal-1#1", +                              "EUR:1", +                              "refresh-melt-1"), +    TALER_TESTING_cmd_recoup ("recoup-1c", +                              MHD_HTTP_OK, +                              "refresh-reveal-1#2", +                              "EUR:1", +                              "refresh-melt-1"), +    /* Now we have EUR:3.83 EUR back after 3x EUR:1 in recoups */ +    /* Melt original coin AGAIN, but only create one 0.1 EUR coin; +       This costs EUR:0.03 in refresh and EUR:01 in withdraw fees, +       leaving EUR:3.69. */ +    TALER_TESTING_cmd_refresh_melt ("refresh-melt-2", +                                    "withdraw-revocation-coin-1", +                                    MHD_HTTP_OK, +                                    "EUR:0.1", +                                    NULL), +    /** +     * Complete (successful) melt operation, and withdraw the coins +     */ +    TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-2", +                                      "refresh-melt-2", +                                      MHD_HTTP_OK), +    /* Revokes refreshed EUR:0.1 coin  */ +    TALER_TESTING_cmd_revoke ("revoke-3-EUR:0.1", +                              MHD_HTTP_OK, +                              "refresh-reveal-2", +                              CONFIG_FILE), +    /* Revoke also original coin denomination */ +    TALER_TESTING_cmd_revoke ("revoke-4-EUR:5", +                              MHD_HTTP_OK, +                              "withdraw-revocation-coin-1", +                              CONFIG_FILE), +    /* Refund coin EUR:0.1 to original coin, creating zombie! */ +    TALER_TESTING_cmd_recoup ("recoup-2", +                              MHD_HTTP_OK, +                              "refresh-reveal-2", +                              "EUR:0.1", +                              "refresh-melt-2"), +    /* Due to recoup, original coin is now at EUR:3.79 */ +    /* Refund original (now zombie) coin to reserve */ +    TALER_TESTING_cmd_recoup ("recoup-3", +                              MHD_HTTP_OK, +                              "withdraw-revocation-coin-1", +                              "EUR:3.79", +                              NULL), +    /* Check the money is back with the reserve */ +    TALER_TESTING_cmd_status ("recoup-reserve-status-1", +                              "create-reserve-1", +                              "EUR:3.79", +                              MHD_HTTP_OK), +    TALER_TESTING_cmd_end () +  }; + +  TALER_TESTING_run_with_fakebank (is, +                                   revocation, +                                   bc.exchange_auth.wire_gateway_url); +} + + +int +main (int argc, +      char *const *argv) +{ +  /* These environment variables get in the way... */ +  unsetenv ("XDG_DATA_HOME"); +  unsetenv ("XDG_CONFIG_HOME"); +  GNUNET_log_setup ("test-exchange-api-revocation", +                    "INFO", +                    NULL); +  /* Check fakebank port is available and get config */ +  if (GNUNET_OK != +      TALER_TESTING_prepare_fakebank (CONFIG_FILE, +                                      "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, +                                          &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; +} + + +/* end of test_exchange_api_revocation.c */ diff --git a/src/testing/test_exchange_api_twisted.c b/src/testing/test_exchange_api_twisted.c new file mode 100644 index 00000000..106cecdc --- /dev/null +++ b/src/testing/test_exchange_api_twisted.c @@ -0,0 +1,332 @@ +/* +  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/test_exchange_api_twisted.c + * @brief testcase to test exchange's HTTP API interface + * @author Marcello Stanisci + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_exchange_service.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" +#include <taler/taler_twister_testing_lib.h> +#include <taler/taler_twister_service.h> + +/** + * Configuration file we use.  One (big) configuration is used + * for the various components for this test. + */ +#define CONFIG_FILE "test_exchange_api_twisted.conf" + +/** + * (real) Twister URL.  Used at startup time to check if it runs. + */ +static char *twister_url; + +/** + * Exchange configuration data. + */ +static struct TALER_TESTING_ExchangeConfiguration ec; + +/** + * Bank configuration data. + */ +static struct TALER_TESTING_BankConfiguration bc; + +/** + * Twister process. + */ +static struct GNUNET_OS_Process *twisterd; + + +/** + * Execute the taler-exchange-wirewatch command with + * our configuration file. + * + * @param label label to use for the command. + */ +static struct TALER_TESTING_Command +CMD_EXEC_WIREWATCH (char *label) +{ +  return TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE); +} + + +/** + * Run wire transfer of funds from some user's account to the + * exchange. + * + * @param label label to use for the command. + * @param amount amount to transfer, i.e. "EUR:1" + * @param url exchange_url + */ +static struct TALER_TESTING_Command +CMD_TRANSFER_TO_EXCHANGE (char *label, char *amount) +{ +  return TALER_TESTING_cmd_admin_add_incoming (label, +                                               amount, +                                               &bc.exchange_auth, +                                               bc.user42_payto); +} + + +/** + * Main function that will tell the interpreter what commands to + * run. + * + * @param cls closure + */ +static void +run (void *cls, +     struct TALER_TESTING_Interpreter *is) +{ +  /** +   * This batch aims to trigger the 409 Conflict +   * response from a refresh-reveal operation. +   */ +  struct TALER_TESTING_Command refresh_409_conflict[] = { +    CMD_TRANSFER_TO_EXCHANGE +      ("refresh-create-reserve", +      "EUR:5.01"), +    /** +     * Make previous command effective. +     */ +    CMD_EXEC_WIREWATCH +      ("wirewatch"), +    /** +     * 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), + +    /** +     * Melt the rest of the coin's value +     * (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */ +    TALER_TESTING_cmd_refresh_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_end () + +  }; + + +  /** +   * NOTE: not all CMDs actually need the twister, +   * so it may be better to move those into the "main" +   * lib test suite. +   */struct TALER_TESTING_Command refund[] = { + +    CMD_TRANSFER_TO_EXCHANGE +      ("create-reserve-r1", +      "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_refund +      ("refund-currency-missmatch", +      MHD_HTTP_PRECONDITION_FAILED, +      "USD:5", +      "USD:0.01", +      "deposit-refund-1"), +    TALER_TESTING_cmd_refund +      ("refund-fee-above-amount", +      MHD_HTTP_BAD_REQUEST, +      "EUR:5", +      "EUR:10", +      "deposit-refund-1"), +    TALER_TESTING_cmd_flip_upload +      ("flip-upload", +      CONFIG_FILE, +      "merchant_sig"), +    TALER_TESTING_cmd_refund +      ("refund-bad-sig", +      MHD_HTTP_FORBIDDEN, +      "EUR:5", +      "EUR:0.01", +      "deposit-refund-1"), + +    /* 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 missmatch.  */ +      "{\"items\":[{\"name\":\"ice skate\"," +      "\"value\":\"EUR:5\"}]}", +      GNUNET_TIME_UNIT_MINUTES, +      "EUR:5", +      MHD_HTTP_CONFLICT), +    TALER_TESTING_cmd_refund +      ("refund-deposit-not-found", +      MHD_HTTP_NOT_FOUND, +      "EUR:5", +      "EUR:0.01", +      "deposit-refund-to-fail"), +    TALER_TESTING_cmd_refund +      ("refund-insufficient-funds", +      MHD_HTTP_PRECONDITION_FAILED, +      "EUR:50", +      "EUR:0.01", +      "deposit-refund-1"), +    TALER_TESTING_cmd_refund +      ("refund-fee-too-low", +      MHD_HTTP_BAD_REQUEST, +      "EUR:5", +      "EUR:0.000001", +      "deposit-refund-1"), +    TALER_TESTING_cmd_end () +  }; + +  struct TALER_TESTING_Command commands[] = { +    TALER_TESTING_cmd_batch ("refresh-reveal-409-conflict", +                             refresh_409_conflict), +    TALER_TESTING_cmd_batch ("refund", +                             refund), +    TALER_TESTING_cmd_end () +  }; + +  TALER_TESTING_run_with_fakebank (is, +                                   commands, +                                   bc.exchange_auth.wire_gateway_url); +} + + +/** + * Kill, wait, and destroy convenience function. + * + * @param process process to purge. + */ +static void +purge_process (struct GNUNET_OS_Process *process) +{ +  GNUNET_OS_process_kill (process, SIGINT); +  GNUNET_OS_process_wait (process); +  GNUNET_OS_process_destroy (process); +} + + +int +main (int argc, +      char *const *argv) +{ +  unsigned int ret; +  /* These environment variables get in the way... */ +  unsetenv ("XDG_DATA_HOME"); +  unsetenv ("XDG_CONFIG_HOME"); +  GNUNET_log_setup ("test-exchange-api-twisted", +                    "DEBUG", NULL); + +  if (GNUNET_OK != +      TALER_TESTING_prepare_fakebank (CONFIG_FILE, +                                      "account-2", +                                      &bc)) +    return 77; + +  if (NULL == (twister_url = TALER_TESTING_prepare_twister +                               (CONFIG_FILE))) +    return 77; + +  TALER_TESTING_cleanup_files (CONFIG_FILE); + +  switch (TALER_TESTING_prepare_exchange (CONFIG_FILE, +                                          &ec)) +  { +  case GNUNET_SYSERR: +    GNUNET_break (0); +    return 1; +  case GNUNET_NO: +    return 77; + +  case GNUNET_OK: + +    if (NULL == (twisterd = TALER_TESTING_run_twister (CONFIG_FILE))) +      return 77; + +    ret = TALER_TESTING_setup_with_exchange (&run, +                                             NULL, +                                             CONFIG_FILE); +    purge_process (twisterd); +    GNUNET_free (twister_url); + +    if (GNUNET_OK != ret) +      return 1; +    break; +  default: +    GNUNET_break (0); +    return 1; +  } +  return 0; +} + + +/* end of test_exchange_api_twisted.c */ diff --git a/src/testing/test_exchange_api_twisted.conf b/src/testing/test_exchange_api_twisted.conf new file mode 100644 index 00000000..28b254e6 --- /dev/null +++ b/src/testing/test_exchange_api_twisted.conf @@ -0,0 +1,196 @@ +# This file is in the public domain. +# + +[PATHS] +# Persistant data storage for the testcase +TALER_TEST_HOME = test_exchange_api_home/ + + +[taler] +# Currency supported by the exchange (can only be one) +CURRENCY = EUR + + +[exchange] + +# how long is one signkey valid? +SIGNKEY_DURATION = 4 weeks + +# how long are the signatures with the signkey valid? +LEGAL_DURATION = 2 years + +# how long do we provide to clients denomination and signing keys +# ahead of time? +LOOKAHEAD_PROVIDE = 4 weeks 1 day + +# Keep it short so the test runs fast. +LOOKAHEAD_SIGN = 12 h + +# HTTP port the exchange listens to +PORT = 8081 + +# Master public key used to sign the exchange's various keys +MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG + +# How to access our database +DB = postgres + +# Base URL of the exchange ('S PROXY).  This URL is where the +# twister listens at, so that it will be able to get all the +# connection addressed to the exchange.  In fact, the presence +# of the twister is 100% transparent to the test case, as it +# only seeks the exchange/BASE_URL URL to connect to the exchange. +BASE_URL = "http://localhost:8888/" + + +[exchangedb-postgres] +CONFIG = "postgres:///talercheck" + + +[auditor] +BASE_URL = "http://localhost:8083/" + +PORT = 8083 + + +[auditordb-postgres] +CONFIG = "postgres:///talercheck" + +[account-1] +# What is the URL of our account? +URL = "payto://x-taler-bank/localhost/42" +# This is the response we give out for the /wire request.  It provides +# wallets with the bank information for transfers to the exchange. +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-1.json +# Which wire plugin should we used to access the account? +METHOD = x-taler-bank +WIRE_GATEWAY_URL = "http://localhost:9081/42/" +WIRE_GATEWAY_AUTH_METHOD = NONE + + +[account-2] +URL = payto://x-taler-bank/localhost/2 +WIRE_GATEWAY_URL = "http://localhost:8082/2/" +WIRE_RESPONSE = ${TALER_CONFIG_HOME}/account-2.json +WIRE_GATEWAY_AUTH_METHOD = BASIC +USERNAME = user +PASSWORD = pass +ENABLE_DEBIT = YES +ENABLE_CREDIT = YES +METHOD = x-taler-bank + + +[bank] +HTTP_PORT = 8082 + + +[twister] +# HTTP listen port for twister +HTTP_PORT = 8888 +SERVE = tcp + +# HTTP Destination for twister.  The test-Webserver needs +# to listen on the port used here.  Note: no trailing '/'! +DESTINATION_BASE_URL = "http://localhost:8081" + +# Control port for TCP +# PORT = 8889 +HOSTNAME = localhost +ACCEPT_FROM = 127.0.0.1; +ACCEPT_FROM6 = ::1; + +# Control port for UNIX +UNIXPATH = /tmp/taler-service-twister.sock +UNIX_MATCH_UID = NO +UNIX_MATCH_GID = YES + +# Launching of twister by ARM +# BINARY = taler-service-twister +# AUTOSTART = NO +# FORCESTART = NO + + +[fees-x-taler-bank] +# Fees for the forseeable future... +# If you see this after 2017, update to match the next 10 years... +WIRE-FEE-2018 = EUR:0.01 +WIRE-FEE-2019 = EUR:0.01 +WIRE-FEE-2020 = EUR:0.01 +WIRE-FEE-2021 = EUR:0.01 +WIRE-FEE-2022 = EUR:0.01 +WIRE-FEE-2023 = EUR:0.01 +WIRE-FEE-2024 = EUR:0.01 +WIRE-FEE-2025 = EUR:0.01 +WIRE-FEE-2026 = EUR:0.01 +WIRE-FEE-2027 = EUR:0.01 + +CLOSING-FEE-2018 = EUR:0.01 +CLOSING-FEE-2019 = EUR:0.01 +CLOSING-FEE-2020 = EUR:0.01 +CLOSING-FEE-2021 = EUR:0.01 +CLOSING-FEE-2022 = EUR:0.01 +CLOSING-FEE-2023 = EUR:0.01 +CLOSING-FEE-2024 = EUR:0.01 +CLOSING-FEE-2025 = EUR:0.01 +CLOSING-FEE-2026 = EUR:0.01 +CLOSING-FEE-2027 = EUR:0.01 + +[coin_eur_ct_1] +value = EUR:0.01 +duration_overlap = 5 minutes +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_overlap = 5 minutes +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_overlap = 5 minutes +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_overlap = 5 minutes +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_overlap = 5 minutes +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_taler_exchange_aggregator.c b/src/testing/test_taler_exchange_aggregator.c new file mode 100644 index 00000000..84695ca0 --- /dev/null +++ b/src/testing/test_taler_exchange_aggregator.c @@ -0,0 +1,524 @@ +/* +  This file is part of TALER +  (C) 2016-2020 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/test_taler_exchange_aggregator.c + * @brief Tests for taler-exchange-aggregator logic + * @author Christian Grothoff <christian@grothoff.org> + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_util.h" +#include <gnunet/gnunet_json_lib.h> +#include "taler_json_lib.h" +#include "taler_exchangedb_lib.h" +#include <microhttpd.h> +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" + + +/** + * Helper structure to keep exchange configuration values. + */ +static struct TALER_TESTING_ExchangeConfiguration ec; + +/** + * Bank configuration data. + */ +static struct TALER_TESTING_BankConfiguration bc; + +/** + * Contains plugin and session. + */ +static struct TALER_TESTING_DatabaseConnection dbc; + +/** + * Return value from main(). + */ +static int result; + +/** + * Name of the configuration file to use. + */ +static char *config_filename; + +#define USER42_ACCOUNT "42" + +/** + * @return GNUNET_NO if database could not be prepared, + * otherwise GNUNET_OK + */ +static int +prepare_database (void *cls, +                  const struct GNUNET_CONFIGURATION_Handle *cfg) +{ +  dbc.plugin = TALER_EXCHANGEDB_plugin_load (cfg); +  if (NULL == dbc.plugin) +  { +    GNUNET_break (0); +    result = 77; +    return GNUNET_NO; +  } +  if (GNUNET_OK != +      dbc.plugin->create_tables (dbc.plugin->cls)) +  { +    GNUNET_break (0); +    TALER_EXCHANGEDB_plugin_unload (dbc.plugin); +    dbc.plugin = NULL; +    result = 77; +    return GNUNET_NO; +  } +  dbc.session = dbc.plugin->get_session (dbc.plugin->cls); +  GNUNET_assert (NULL != dbc.session); + +  return GNUNET_OK; +} + + +/** + * Collects all the tests. + */ +static void +run (void *cls, +     struct TALER_TESTING_Interpreter *is) +{ +  struct TALER_TESTING_Command all[] = { + +    // check no aggregation happens on a empty database +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-on-empty-db", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-on-start"), + +    /* check aggregation happens on the simplest case: +       one deposit into the database. */ +    TALER_TESTING_cmd_insert_deposit ("do-deposit-1", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:1", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-on-deposit-1", +                                       config_filename), + +    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-1", +                                           ec.exchange_url, +                                           "EUR:0.89", +                                           bc.exchange_payto, +                                           bc.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, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:1", +                                      "EUR:0.1"), + +    TALER_TESTING_cmd_insert_deposit ("do-deposit-2b", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:1", +                                      "EUR:0.1"), + +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-2", +                                       config_filename), + +    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-2", +                                           ec.exchange_url, +                                           "EUR:1.79", +                                           bc.exchange_payto, +                                           bc.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, +                                      "bob", +                                      "4", +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:1", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_insert_deposit ("do-deposit-3b", +                                      &dbc, +                                      "bob", +                                      "5", +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:1", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_insert_deposit ("do-deposit-3c", +                                      &dbc, +                                      "alice", +                                      "4", +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:1", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-3", +                                       config_filename), + +    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-3a", +                                           ec.exchange_url, +                                           "EUR:0.89", +                                           bc.exchange_payto, +                                           "payto://x-taler-bank/localhost/4"), +    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-3b", +                                           ec.exchange_url, +                                           "EUR:0.89", +                                           bc.exchange_payto, +                                           "payto://x-taler-bank/localhost/4"), +    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-3c", +                                           ec.exchange_url, +                                           "EUR:0.89", +                                           bc.exchange_payto, +                                           "payto://x-taler-bank/localhost/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, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_relative_multiply +                                        (GNUNET_TIME_UNIT_SECONDS, +                                        5), +                                      "EUR:0.2", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_insert_deposit ("do-deposit-4b", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_relative_multiply +                                        (GNUNET_TIME_UNIT_SECONDS, +                                        5), +                                      "EUR:0.2", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-4-early", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_empty ( +      "expect-empty-transactions-after-4-fast"), + +    TALER_TESTING_cmd_sleep ("wait (5s)", 5), + +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-4-delayed", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-4", +                                           ec.exchange_url, +                                           "EUR:0.19", +                                           bc.exchange_payto, +                                           bc.user42_payto), + +    // test picking all deposits at earliest deadline +    TALER_TESTING_cmd_insert_deposit ("do-deposit-5a", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_relative_multiply +                                        (GNUNET_TIME_UNIT_SECONDS, +                                        10), +                                      "EUR:0.2", +                                      "EUR:0.1"), + +    TALER_TESTING_cmd_insert_deposit ("do-deposit-5b", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_relative_multiply +                                        (GNUNET_TIME_UNIT_SECONDS, +                                        5), +                                      "EUR:0.2", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-5-early", +                                       config_filename), + +    TALER_TESTING_cmd_check_bank_empty ( +      "expect-empty-transactions-after-5-early"), +    TALER_TESTING_cmd_sleep ("wait (5s)", 5), + +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-5-delayed", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-5", +                                           ec.exchange_url, +                                           "EUR:0.19", +                                           bc.exchange_payto, +                                           bc.user42_payto), +    /* Test NEVER running 'tiny' unless they make up minimum unit */ +    TALER_TESTING_cmd_insert_deposit ("do-deposit-6a", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:0.102", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-6a-tiny", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_empty ( +      "expect-empty-transactions-after-6a-tiny"), +    TALER_TESTING_cmd_insert_deposit ("do-deposit-6b", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:0.102", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_insert_deposit ("do-deposit-6c", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:0.102", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-6c-tiny", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_empty ( +      "expect-empty-transactions-after-6c-tiny"), +    TALER_TESTING_cmd_insert_deposit ("do-deposit-6d", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:0.102", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-6d-tiny", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_empty ( +      "expect-empty-transactions-after-6d-tiny"), +    TALER_TESTING_cmd_insert_deposit ("do-deposit-6e", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:0.112", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-6e", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-6", +                                           ec.exchange_url, +                                           "EUR:0.01", +                                           bc.exchange_payto, +                                           bc.user42_payto), + +    /* Test profiteering if wire deadline is short */ +    TALER_TESTING_cmd_insert_deposit ("do-deposit-7a", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:0.109", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-7a-tiny", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_empty ( +      "expect-empty-transactions-after-7a-tiny"), +    TALER_TESTING_cmd_insert_deposit ("do-deposit-7b", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:0.119", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-7-profit", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-7", +                                           ec.exchange_url, +                                           "EUR:0.01", +                                           bc.exchange_payto, +                                           bc.user42_payto), + +    /* Now check profit was actually taken */ +    TALER_TESTING_cmd_insert_deposit ("do-deposit-7c", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:0.122", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-7-loss", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-7", +                                           ec.exchange_url, +                                           "EUR:0.01", +                                           bc.exchange_payto, +                                           bc.user42_payto), + +    /* Test that aggregation would happen fully if wire deadline is long */ +    TALER_TESTING_cmd_insert_deposit ("do-deposit-8a", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_relative_multiply +                                        (GNUNET_TIME_UNIT_SECONDS, +                                        5), +                                      "EUR:0.109", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-8a-tiny", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_empty ( +      "expect-empty-transactions-after-8a-tiny"), +    TALER_TESTING_cmd_insert_deposit ("do-deposit-8b", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_relative_multiply +                                        (GNUNET_TIME_UNIT_SECONDS, +                                        5), +                                      "EUR:0.109", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-8b-tiny", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_empty ( +      "expect-empty-transactions-after-8b-tiny"), + +    /* now trigger aggregate with large transaction and short deadline */ +    TALER_TESTING_cmd_insert_deposit ("do-deposit-8c", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:0.122", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-8", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-8", +                                           ec.exchange_url, +                                           "EUR:0.03", +                                           bc.exchange_payto, +                                           bc.user42_payto), + +    /* Test aggregation with fees and rounding profits. */ +    TALER_TESTING_cmd_insert_deposit ("do-deposit-9a", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_relative_multiply +                                        (GNUNET_TIME_UNIT_SECONDS, +                                        5), +                                      "EUR:0.104", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-9a-tiny", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_empty ( +      "expect-empty-transactions-after-9a-tiny"), +    TALER_TESTING_cmd_insert_deposit ("do-deposit-9b", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_relative_multiply +                                        (GNUNET_TIME_UNIT_SECONDS, +                                        5), +                                      "EUR:0.105", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-9b-tiny", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_empty ( +      "expect-empty-transactions-after-9b-tiny"), + +    /* now trigger aggregate with large transaction and short deadline */ +    TALER_TESTING_cmd_insert_deposit ("do-deposit-9c", +                                      &dbc, +                                      "bob", +                                      USER42_ACCOUNT, +                                      GNUNET_TIME_UNIT_ZERO, +                                      "EUR:0.112", +                                      "EUR:0.1"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-deposit-9", +                                       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, +                                           "EUR:0.01", +                                           bc.exchange_payto, +                                           bc.user42_payto), +    TALER_TESTING_cmd_end () +  }; + +  TALER_TESTING_run_with_fakebank (is, +                                   all, +                                   bc.exchange_auth.wire_gateway_url); +} + + +int +main (int argc, +      char *const argv[]) +{ +  const char *plugin_name; +  char *testname; + +  if (NULL == (plugin_name = strrchr (argv[0], (int) '-'))) +  { +    GNUNET_break (0); +    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, +                                                   &ec)) +  { +    TALER_LOG_WARNING ("Could not prepare the exchange.\n"); +    return 77; +  } + +  if (GNUNET_OK != TALER_TESTING_prepare_fakebank (config_filename, +                                                   "account-1", +                                                   &bc)) +  { +    TALER_LOG_WARNING ("Could not prepare the fakebank\n"); +    return 77; +  } + +  if (GNUNET_OK != GNUNET_CONFIGURATION_parse_and_run (config_filename, +                                                       &prepare_database, +                                                       NULL)) +  { +    TALER_LOG_WARNING ("Could not prepare database for tests.\n"); +    return result; +  } + +  result = TALER_TESTING_setup (&run, +                                NULL, +                                config_filename, +                                NULL, // no exchange process handle. +                                GNUNET_NO); // do not try to connect to the exchange + +  GNUNET_free (config_filename); +  GNUNET_free (testname); +  dbc.plugin->drop_tables (dbc.plugin->cls); +  TALER_EXCHANGEDB_plugin_unload (dbc.plugin); +  return GNUNET_OK == result ? 0 : 1; +} + + +/* end of test_taler_exchange_aggregator.c */ 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 new file mode 100644 index 00000000..f39677ef --- /dev/null +++ b/src/testing/test_taler_exchange_httpd_home/.config/taler/account-1.json @@ -0,0 +1,4 @@ +{ +  "url": "payto://x-taler-bank/localhost/2", +  "master_sig": "HEWC1XDS0QZ53YQR451VRKD4N968NXWGZXS30HJ59MJ0PESACK1ZYPYCAT15P08WD58C7D7F6EVN26D59JKA75XEBDQCM8VYFETK82R" +}
\ No newline at end of file diff --git a/src/testing/test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv b/src/testing/test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv new file mode 100644 index 00000000..39492693 --- /dev/null +++ b/src/testing/test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv @@ -0,0 +1 @@ +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 new file mode 100644 index 00000000..5a202bef --- /dev/null +++ b/src/testing/test_taler_exchange_wirewatch.c @@ -0,0 +1,182 @@ +/* +  This file is part of TALER +  (C) 2016, 2017, 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it under the +  terms of the GNU General Public License as published by the Free Software +  Foundation; either version 3, or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but WITHOUT ANY +  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +  A PARTICULAR PURPOSE.  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/test_taler_exchange_wirewatch.c + * @brief Tests for taler-exchange-wirewatch and taler-exchange-aggregator logic; + *        Performs an invalid wire transfer to the exchange, and then checks that + *        wirewatch immediately sends the money back. + *        Then performs a valid wire transfer, waits for the reserve to expire, + *        and then checks that the aggregator sends the money back. + * @author Christian Grothoff <christian@grothoff.org> + */ +#include "platform.h" +#include "taler_util.h" +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_pq_lib.h> +#include "taler_json_lib.h" +#include <microhttpd.h> +#include "taler_fakebank_lib.h" +#include "taler_testing_lib.h" + + +/** + * Bank configuration data. + */ +static struct TALER_TESTING_BankConfiguration bc; + +/** + * Helper structure to keep exchange configuration values. + */ +static struct TALER_TESTING_ExchangeConfiguration ec; + +/** + * Name of the configuration file to use. + */ +static char *config_filename; + +static struct TALER_TESTING_Command +transfer_to_exchange (const char *label, +                      const char *amount) +{ +  return TALER_TESTING_cmd_admin_add_incoming (label, +                                               amount, +                                               &bc.exchange_auth, +                                               bc.user42_payto); +} + + +/** + * Main function that will tell the interpreter what commands to + * run. + * + * @param cls closure + */ +static void +run (void *cls, +     struct TALER_TESTING_Interpreter *is) +{ +  struct TALER_TESTING_Command all[] = { +    TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-on-start"), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-on-empty", +                                       config_filename), +    TALER_TESTING_cmd_exec_wirewatch ("run-wirewatch-on-empty", +                                      config_filename), +    TALER_TESTING_cmd_check_bank_empty ("expect-transfers-empty-after-dry-run"), + +    transfer_to_exchange ("run-transfer-good-to-exchange", +                          "EUR:5"), +    TALER_TESTING_cmd_exec_wirewatch ("run-wirewatch-on-good-transfer", +                                      config_filename), + +    TALER_TESTING_cmd_check_bank_admin_transfer ( +      "clear-good-transfer-to-the-exchange", +      "EUR:5", +      bc.user42_payto,                                            // debit +      bc.exchange_payto,                                            // credit +      "run-transfer-good-to-exchange"), + +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-non-expired-reserve", +                                       config_filename), + +    TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-1"), +    TALER_TESTING_cmd_sleep ("wait (5s)", +                             5), +    TALER_TESTING_cmd_exec_aggregator ("run-aggregator-on-expired-reserve", +                                       config_filename), +    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-1", +                                           ec.exchange_url, +                                           "EUR:4.99", +                                           bc.exchange_payto, +                                           bc.user42_payto), +    TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-2"), +    TALER_TESTING_cmd_end () +  }; + +  TALER_TESTING_run_with_fakebank (is, +                                   all, +                                   bc.exchange_auth.wire_gateway_url); +} + + +int +main (int argc, +      char *const argv[]) +{ +  const char *plugin_name; + +  /* these might get in the way */ +  unsetenv ("XDG_DATA_HOME"); +  unsetenv ("XDG_CONFIG_HOME"); +  GNUNET_log_setup ("test_taler_exchange_wirewatch", +                    "DEBUG", +                    NULL); + +  if (NULL == (plugin_name = strrchr (argv[0], (int) '-'))) +  { +    GNUNET_break (0); +    return -1; +  } +  plugin_name++; +  { +    char *testname; + +    GNUNET_asprintf (&testname, +                     "test-taler-exchange-wirewatch-%s", +                     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, +                                                   &ec)) +  { +    TALER_LOG_INFO ("Could not prepare the exchange\n"); +    return 77; +  } + +  if (GNUNET_OK != +      TALER_TESTING_prepare_fakebank (config_filename, +                                      "account-1", +                                      &bc)) +    return 77; + +  return +    (GNUNET_OK == TALER_TESTING_setup_with_exchange (&run, +                                                     NULL, +                                                     config_filename)) ? 0 : 1; +} + + +/* end of test_taler_exchange_wirewatch.c */ diff --git a/src/testing/testing_api_cmd_auditor_deposit_confirmation.c b/src/testing/testing_api_cmd_auditor_deposit_confirmation.c new file mode 100644 index 00000000..6115ceef --- /dev/null +++ b/src/testing/testing_api_cmd_auditor_deposit_confirmation.c @@ -0,0 +1,444 @@ +/* +  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_deposit_confirmation.c + * @brief command for testing /deposit_confirmation. + * @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" + + +/** + * State for a "deposit confirmation" CMD. + */ +struct DepositConfirmationState +{ + +  /** +   * Reference to any command that is able to provide a deposit. +   */ +  const char *deposit_reference; + +  /** +   * What is the deposited amount without the fee (i.e. the +   * amount we expect in the deposit confirmation)? +   */ +  const char *amount_without_fee; + +  /** +   * Which coin of the @e deposit_reference should we confirm. +   */ +  unsigned int coin_index; + +  /** +   * DepositConfirmation handle while operation is running. +   */ +  struct TALER_AUDITOR_DepositConfirmationHandle *dc; + +  /** +   * 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; + +  /** +   * Should we retry on (transient) failures? +   */ +  int do_retry; + +}; + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +deposit_confirmation_run (void *cls, +                          const struct TALER_TESTING_Command *cmd, +                          struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #deposit_confirmation_run. + * + * @param cls a `struct DepositConfirmationState` + */ +static void +do_retry (void *cls) +{ +  struct DepositConfirmationState *dcs = cls; + +  dcs->retry_task = NULL; +  deposit_confirmation_run (dcs, +                            NULL, +                            dcs->is); +} + + +/** + * Callback to analyze the /deposit-confirmation response, just used + * to check if the response code is acceptable. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param obj raw response from the auditor. + */ +static void +deposit_confirmation_cb (void *cls, +                         unsigned int http_status, +                         enum TALER_ErrorCode ec, +                         const json_t *obj) +{ +  struct DepositConfirmationState *dcs = cls; + +  dcs->dc = NULL; +  if (dcs->expected_response_code != http_status) +  { +    if (GNUNET_YES == dcs->do_retry) +    { +      if ( (0 == http_status) || +           (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || +           (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                    "Retrying deposit confirmation failed with %u/%d\n", +                    http_status, +                    (int) ec); +        /* on DB conflicts, do not use backoff */ +        if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) +          dcs->backoff = GNUNET_TIME_UNIT_ZERO; +        else +          dcs->backoff = EXCHANGE_LIB_BACKOFF (dcs->backoff); +        dcs->retry_task = GNUNET_SCHEDULER_add_delayed (dcs->backoff, +                                                        &do_retry, +                                                        dcs); +        return; +      } +    } +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s in %s:%u\n", +                http_status, +                dcs->is->commands[dcs->is->ip].label, +                __FILE__, +                __LINE__); +    json_dumpf (obj, stderr, 0); +    TALER_TESTING_interpreter_fail (dcs->is); +    return; +  } +  TALER_TESTING_interpreter_next (dcs->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +deposit_confirmation_run (void *cls, +                          const struct TALER_TESTING_Command *cmd, +                          struct TALER_TESTING_Interpreter *is) +{ +  struct DepositConfirmationState *dcs = cls; +  const struct TALER_TESTING_Command *deposit_cmd; +  struct GNUNET_HashCode h_wire; +  struct GNUNET_HashCode h_contract_terms; +  struct GNUNET_TIME_Absolute timestamp; +  struct GNUNET_TIME_Absolute refund_deadline; +  struct TALER_Amount amount_without_fee; +  struct TALER_CoinSpendPublicKeyP coin_pub; +  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; + +  dcs->is = is; +  GNUNET_assert (NULL != dcs->deposit_reference); +  deposit_cmd +    = TALER_TESTING_interpreter_lookup_command (is, +                                                dcs->deposit_reference); +  if (NULL == deposit_cmd) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  GNUNET_assert (GNUNET_OK == +                 TALER_TESTING_get_trait_exchange_pub (deposit_cmd, +                                                       dcs->coin_index, +                                                       &exchange_pub)); +  GNUNET_assert (GNUNET_OK == +                 TALER_TESTING_get_trait_exchange_sig (deposit_cmd, +                                                       dcs->coin_index, +                                                       &exchange_sig)); +  keys = TALER_EXCHANGE_get_keys (dcs->is->exchange); +  GNUNET_assert (NULL != keys); +  spk = TALER_EXCHANGE_get_exchange_signing_key_info (keys, +                                                      exchange_pub); + +  GNUNET_assert (GNUNET_OK == +                 TALER_TESTING_get_trait_contract_terms (deposit_cmd, +                                                         dcs->coin_index, +                                                         &contract_terms)); +  /* Very unlikely to fail */ +  GNUNET_assert (NULL != contract_terms); +  GNUNET_assert (GNUNET_OK == +                 TALER_JSON_hash (contract_terms, +                                  &h_contract_terms)); +  GNUNET_assert (GNUNET_OK == +                 TALER_TESTING_get_trait_wire_details (deposit_cmd, +                                                       dcs->coin_index, +                                                       &wire_details)); +  GNUNET_assert (GNUNET_OK == +                 TALER_JSON_merchant_wire_signature_hash (wire_details, +                                                          &h_wire)); +  GNUNET_assert (GNUNET_OK == +                 TALER_TESTING_get_trait_coin_priv (deposit_cmd, +                                                    dcs->coin_index, +                                                    &coin_priv)); +  GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, +                                      &coin_pub.eddsa_pub); +  GNUNET_assert (GNUNET_OK == +                 TALER_TESTING_get_trait_merchant_priv (deposit_cmd, +                                                        dcs->coin_index, +                                                        &merchant_priv)); +  GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv, +                                      &merchant_pub.eddsa_pub); +  GNUNET_assert (GNUNET_OK == +                 TALER_string_to_amount (dcs->amount_without_fee, +                                         &amount_without_fee)); +  /* timestamp is mandatory */ +  { +    struct GNUNET_JSON_Specification spec[] = { +      GNUNET_JSON_spec_absolute_time ("timestamp", ×tamp), +      GNUNET_JSON_spec_end () +    }; + +    if (GNUNET_OK != +        GNUNET_JSON_parse (contract_terms, +                           spec, +                           NULL, NULL)) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (is); +      return; +    } +  } +  /* refund deadline is optional, defaults to zero */ +  { +    struct GNUNET_JSON_Specification spec[] = { +      GNUNET_JSON_spec_absolute_time ("refund_deadline", &refund_deadline), +      GNUNET_JSON_spec_end () +    }; + +    if (GNUNET_OK != +        GNUNET_JSON_parse (contract_terms, +                           spec, +                           NULL, NULL)) +    { +      refund_deadline = timestamp; +    } +  } +  dcs->dc = TALER_AUDITOR_deposit_confirmation +              (dcs->auditor, +              &h_wire, +              &h_contract_terms, +              timestamp, +              refund_deadline, +              &amount_without_fee, +              &coin_pub, +              &merchant_pub, +              exchange_pub, +              exchange_sig, +              &keys->master_pub, +              spk->valid_from, +              spk->valid_until, +              spk->valid_legal, +              &spk->master_sig, +              &deposit_confirmation_cb, +              dcs); + +  if (NULL == dcs->dc) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  return; +} + + +/** + * Free the state of a "deposit_confirmation" CMD, and possibly cancel a + * pending operation thereof. + * + * @param cls closure, a `struct DepositConfirmationState` + * @param cmd the command which is being cleaned up. + */ +static void +deposit_confirmation_cleanup (void *cls, +                              const struct TALER_TESTING_Command *cmd) +{ +  struct DepositConfirmationState *dcs = cls; + +  if (NULL != dcs->dc) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Command %u (%s) did not complete\n", +                dcs->is->ip, +                cmd->label); +    TALER_AUDITOR_deposit_confirmation_cancel (dcs->dc); +    dcs->dc = NULL; +  } +  if (NULL != dcs->retry_task) +  { +    GNUNET_SCHEDULER_cancel (dcs->retry_task); +    dcs->retry_task = NULL; +  } +  GNUNET_free (dcs); +} + + +/** + * Offer internal data to other commands. + * + * @param cls closure. + * @param[out] ret set to the wanted data. + * @param trait name of the trait. + * @param index index number of the traits to be returned. + * + * @return #GNUNET_OK on success + */ +static int +deposit_confirmation_traits (void *cls, +                             const void **ret, +                             const char *trait, +                             unsigned int index) +{ +  /* Must define this function because some callbacks +   * look for certain traits on _all_ the commands. */ +  return GNUNET_SYSERR; +} + + +/** + * Create a "deposit-confirmation" command. + * + * @param label command label. + * @param auditor auditor connection. + * @param deposit_reference reference to any operation that can + *        provide a coin. + * @param coin_index if @a deposit_reference offers an array of + *        coins, this parameter selects which one in that array. + *        This value is currently ignored, as only one-coin + *        deposits are implemented. + * @param amount_without_fee deposited amount without the fee + * @param expected_response_code expected HTTP response code. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_deposit_confirmation (const char *label, +                                        struct TALER_AUDITOR_Handle *auditor, +                                        const char *deposit_reference, +                                        unsigned int coin_index, +                                        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->amount_without_fee = amount_without_fee; +  dcs->expected_response_code = expected_response_code; + +  { +    struct TALER_TESTING_Command cmd = { +      .cls = dcs, +      .label = label, +      .run = &deposit_confirmation_run, +      .cleanup = &deposit_confirmation_cleanup, +      .traits = &deposit_confirmation_traits +    }; + +    return cmd; +  } +} + + +/** + * Modify a deposit confirmation command to enable retries when we get + * transient errors from the auditor. + * + * @param cmd a deposit confirmation command + * @return the command with retries enabled + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_deposit_confirmation_with_retry (struct TALER_TESTING_Command +                                                   cmd) +{ +  struct DepositConfirmationState *dcs; + +  GNUNET_assert (&deposit_confirmation_run == cmd.run); +  dcs = cmd.cls; +  dcs->do_retry = GNUNET_YES; +  return cmd; +} + + +/* end of testing_auditor_api_cmd_deposit_confirmation.c */ diff --git a/src/testing/testing_api_cmd_auditor_exchanges.c b/src/testing/testing_api_cmd_auditor_exchanges.c new file mode 100644 index 00000000..c7acaab9 --- /dev/null +++ b/src/testing/testing_api_cmd_auditor_exchanges.c @@ -0,0 +1,361 @@ +/* +  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" + + +/** + * 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; + +  /** +   * Should we retry on (transient) failures? +   */ +  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; +  exchanges_run (es, +                 NULL, +                 es->is); +} + + +/** + * Callback to analyze the /exchanges response. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param num_exchanges length of the @a ei array + * @param ei array with information about the exchanges + * @param raw_response raw response from the auditor. + */ +static void +exchanges_cb (void *cls, +              unsigned int http_status, +              enum TALER_ErrorCode ec, +              unsigned int num_exchanges, +              const struct TALER_AUDITOR_ExchangeInfo *ei, +              const json_t *raw_response) +{ +  struct ExchangesState *es = cls; + +  es->leh = NULL; +  if (es->expected_response_code != http_status) +  { +    if (GNUNET_YES == es->do_retry) +    { +      if ( (0 == http_status) || +           (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || +           (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                    "Retrying list exchanges failed with %u/%d\n", +                    http_status, +                    (int) ec); +        /* on DB conflicts, do not use backoff */ +        if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) +          es->backoff = GNUNET_TIME_UNIT_ZERO; +        else +          es->backoff = EXCHANGE_LIB_BACKOFF (es->backoff); +        es->retry_task = GNUNET_SCHEDULER_add_delayed (es->backoff, +                                                       &do_retry, +                                                       es); +        return; +      } +    } +    GNUNET_log +      (GNUNET_ERROR_TYPE_ERROR, +      "Unexpected response code %u to command %s in %s:%u\n", +      http_status, +      es->is->commands[es->is->ip].label, +      __FILE__, +      __LINE__); +    json_dumpf (raw_response, stderr, 0); +    TALER_TESTING_interpreter_fail (es->is); +    return; +  } +  if (NULL != es->exchange_url) +  { +    unsigned int found = GNUNET_NO; + +    for (unsigned int i = 0; +         i<num_exchanges; +         i++) +      if (0 == strcmp (es->exchange_url, +                       ei[i].exchange_url)) +        found = GNUNET_YES; +    if (GNUNET_NO == found) +    { +      TALER_LOG_ERROR +        ("Exchange '%s' doesn't exist at this auditor\n", +        es->exchange_url); +      TALER_TESTING_interpreter_fail (es->is); +      return; +    } + +    TALER_LOG_DEBUG ("Exchange '%s' exists at this auditor!\n", +                     es->exchange_url); +  } +  TALER_TESTING_interpreter_next (es->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +exchanges_run (void *cls, +               const struct TALER_TESTING_Command *cmd, +               struct TALER_TESTING_Interpreter *is) +{ +  struct ExchangesState *es = cls; + +  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) +{ +  /* 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 = GNUNET_YES; +  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 new file mode 100644 index 00000000..4e071d0c --- /dev/null +++ b/src/testing/testing_api_cmd_auditor_exec_auditor.c @@ -0,0 +1,165 @@ +/* +  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_exec_auditor.c + * @brief run the taler-auditor command + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "auditor" CMD. + */ +struct AuditorState +{ + +  /** +   * Process for the "auditor" command. +   */ +  struct GNUNET_OS_Process *auditor_proc; + +  /** +   * Configuration file used by the command. +   */ +  const char *config_filename; +}; + + +/** + * Run the command; calls the `taler-auditor' program. + * + * @param cls closure. + * @param cmd the commaind being run. + * @param is interpreter state. + */ +static void +auditor_run (void *cls, +             const struct TALER_TESTING_Command *cmd, +             struct TALER_TESTING_Interpreter *is) +{ +  struct AuditorState *ks = cls; + +  ks->auditor_proc +    = GNUNET_OS_start_process (GNUNET_NO, +                               GNUNET_OS_INHERIT_STD_ALL, +                               NULL, NULL, NULL, +                               "taler-auditor", +                               "taler-auditor", +                               "-c", ks->config_filename, +                               NULL); +  if (NULL == ks->auditor_proc) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "auditor" CMD, and possibly kills its + * process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +auditor_cleanup (void *cls, +                 const struct TALER_TESTING_Command *cmd) +{ +  struct AuditorState *ks = cls; + +  if (NULL != ks->auditor_proc) +  { +    GNUNET_break (0 == +                  GNUNET_OS_process_kill (ks->auditor_proc, +                                          SIGKILL)); +    GNUNET_OS_process_wait (ks->auditor_proc); +    GNUNET_OS_process_destroy (ks->auditor_proc); +    ks->auditor_proc = NULL; +  } +  GNUNET_free (ks); +} + + +/** + * Offer "auditor" CMD internal data to other commands. + * + * @param cls closure. + * @param[out] ret result + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +auditor_traits (void *cls, +                const void **ret, +                const char *trait, +                unsigned int index) +{ +  struct AuditorState *ks = cls; +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_make_trait_process (0, &ks->auditor_proc), +    TALER_TESTING_trait_end () +  }; + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Make the "exec-auditor" CMD. + * + * @param label command label. + * @param config_filename configuration filename. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_auditor (const char *label, +                                const char *config_filename) +{ +  struct AuditorState *ks; + +  ks = GNUNET_new (struct AuditorState); +  ks->config_filename = config_filename; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ks, +      .label = label, +      .run = &auditor_run, +      .cleanup = &auditor_cleanup, +      .traits = &auditor_traits +    }; + +    return cmd; +  } +} + + +/* end of testing_auditor_api_cmd_exec_auditor.c */ diff --git a/src/testing/testing_api_cmd_auditor_exec_auditor_dbinit.c b/src/testing/testing_api_cmd_auditor_exec_auditor_dbinit.c new file mode 100644 index 00000000..0c537747 --- /dev/null +++ b/src/testing/testing_api_cmd_auditor_exec_auditor_dbinit.c @@ -0,0 +1,166 @@ +/* +  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_exec_auditor_dbinit.c + * @brief run the taler-auditor-dbinit "-r" command + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "auditor-dbinit" CMD. + */ +struct AuditorDbinitState +{ + +  /** +   * Process for the "auditor-dbinit" command. +   */ +  struct GNUNET_OS_Process *auditor_dbinit_proc; + +  /** +   * Configuration file used by the command. +   */ +  const char *config_filename; +}; + + +/** + * Run the command; calls the `taler-auditor-dbinit' program. + * + * @param cls closure. + * @param cmd the commaind being run. + * @param is interpreter state. + */ +static void +auditor_dbinit_run (void *cls, +                    const struct TALER_TESTING_Command *cmd, +                    struct TALER_TESTING_Interpreter *is) +{ +  struct AuditorDbinitState *ks = cls; + +  ks->auditor_dbinit_proc +    = GNUNET_OS_start_process (GNUNET_NO, +                               GNUNET_OS_INHERIT_STD_ALL, +                               NULL, NULL, NULL, +                               "taler-auditor-dbinit", +                               "taler-auditor-dbinit", +                               "-c", ks->config_filename, +                               "-r", +                               NULL); +  if (NULL == ks->auditor_dbinit_proc) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "auditor-dbinit" CMD, and possibly kills its + * process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +auditor_dbinit_cleanup (void *cls, +                        const struct TALER_TESTING_Command *cmd) +{ +  struct AuditorDbinitState *ks = cls; + +  if (NULL != ks->auditor_dbinit_proc) +  { +    GNUNET_break (0 == +                  GNUNET_OS_process_kill (ks->auditor_dbinit_proc, +                                          SIGKILL)); +    GNUNET_OS_process_wait (ks->auditor_dbinit_proc); +    GNUNET_OS_process_destroy (ks->auditor_dbinit_proc); +    ks->auditor_dbinit_proc = NULL; +  } +  GNUNET_free (ks); +} + + +/** + * Offer "auditor-dbinit" CMD internal data to other commands. + * + * @param cls closure. + * @param[out] ret result + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +auditor_dbinit_traits (void *cls, +                       const void **ret, +                       const char *trait, +                       unsigned int index) +{ +  struct AuditorDbinitState *ks = cls; +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_make_trait_process (0, &ks->auditor_dbinit_proc), +    TALER_TESTING_trait_end () +  }; + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Make the "exec-auditor-dbinit" CMD. + * + * @param label command label. + * @param config_filename configuration filename. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_auditor_dbinit (const char *label, +                                       const char *config_filename) +{ +  struct AuditorDbinitState *ks; + +  ks = GNUNET_new (struct AuditorDbinitState); +  ks->config_filename = config_filename; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ks, +      .label = label, +      .run = &auditor_dbinit_run, +      .cleanup = &auditor_dbinit_cleanup, +      .traits = &auditor_dbinit_traits +    }; + +    return cmd; +  } +} + + +/* end of testing_auditor_api_cmd_exec_auditor_dbinit.c */ diff --git a/src/testing/testing_api_cmd_auditor_exec_wire_auditor.c b/src/testing/testing_api_cmd_auditor_exec_wire_auditor.c new file mode 100644 index 00000000..5e3701fc --- /dev/null +++ b/src/testing/testing_api_cmd_auditor_exec_wire_auditor.c @@ -0,0 +1,165 @@ +/* +  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_exec_wire_auditor.c + * @brief run the taler-wire-auditor command + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "wire-auditor" CMD. + */ +struct WireAuditorState +{ + +  /** +   * Process for the "wire-auditor" command. +   */ +  struct GNUNET_OS_Process *wire_auditor_proc; + +  /** +   * Configuration file used by the command. +   */ +  const char *config_filename; +}; + + +/** + * Run the command; calls the `taler-wire-auditor' program. + * + * @param cls closure. + * @param cmd the commaind being run. + * @param is interpreter state. + */ +static void +wire_auditor_run (void *cls, +                  const struct TALER_TESTING_Command *cmd, +                  struct TALER_TESTING_Interpreter *is) +{ +  struct WireAuditorState *ks = cls; + +  ks->wire_auditor_proc +    = GNUNET_OS_start_process (GNUNET_NO, +                               GNUNET_OS_INHERIT_STD_ALL, +                               NULL, NULL, NULL, +                               "taler-wire-auditor", +                               "taler-wire-auditor", +                               "-c", ks->config_filename, +                               NULL); +  if (NULL == ks->wire_auditor_proc) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "wire-auditor" CMD, and possibly kills its + * process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +wire_auditor_cleanup (void *cls, +                      const struct TALER_TESTING_Command *cmd) +{ +  struct WireAuditorState *ks = cls; + +  if (NULL != ks->wire_auditor_proc) +  { +    GNUNET_break (0 == +                  GNUNET_OS_process_kill (ks->wire_auditor_proc, +                                          SIGKILL)); +    GNUNET_OS_process_wait (ks->wire_auditor_proc); +    GNUNET_OS_process_destroy (ks->wire_auditor_proc); +    ks->wire_auditor_proc = NULL; +  } +  GNUNET_free (ks); +} + + +/** + * Offer "wire-auditor" CMD internal data to other commands. + * + * @param cls closure. + * @param[out] ret result + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +wire_auditor_traits (void *cls, +                     const void **ret, +                     const char *trait, +                     unsigned int index) +{ +  struct WireAuditorState *ks = cls; +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_make_trait_process (0, &ks->wire_auditor_proc), +    TALER_TESTING_trait_end () +  }; + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Make the "exec wire-auditor" CMD. + * + * @param label command label. + * @param config_filename configuration filename. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_wire_auditor (const char *label, +                                     const char *config_filename) +{ +  struct WireAuditorState *ks; + +  ks = GNUNET_new (struct WireAuditorState); +  ks->config_filename = config_filename; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ks, +      .label = label, +      .run = &wire_auditor_run, +      .cleanup = &wire_auditor_cleanup, +      .traits = &wire_auditor_traits +    }; + +    return cmd; +  } +} + + +/* end of testing_auditor_api_cmd_exec_wire_auditor.c */ diff --git a/src/testing/testing_api_cmd_bank_admin_add_incoming.c b/src/testing/testing_api_cmd_bank_admin_add_incoming.c new file mode 100644 index 00000000..2398c5be --- /dev/null +++ b/src/testing/testing_api_cmd_bank_admin_add_incoming.c @@ -0,0 +1,596 @@ +/* +  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_cmd_bank_admin_add_incoming.c + * @brief implementation of a bank /admin/add-incoming command + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "backoff.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "fakebank transfer" CMD. + */ +struct AdminAddIncomingState +{ + +  /** +   * Label of any command that can trait-offer a reserve priv. +   */ +  const char *reserve_reference; + +  /** +   * Wire transfer amount. +   */ +  struct TALER_Amount amount; + +  /** +   * Base URL of the credited account. +   */ +  const char *exchange_credit_url; + +  /** +   * Money sender payto URL. +   */ +  const char *payto_debit_account; + +  /** +   * Username to use for authentication. +   */ +  struct TALER_BANK_AuthenticationData auth; + +  /** +   * Set (by the interpreter) to the reserve's private key +   * we used to make a wire transfer subject line with. +   */ +  struct TALER_ReservePrivateKeyP reserve_priv; + +  /** +   * Reserve public key matching @e reserve_priv. +   */ +  struct TALER_ReservePublicKeyP reserve_pub; + +  /** +   * Handle to the pending request at the fakebank. +   */ +  struct TALER_BANK_AdminAddIncomingHandle *aih; + +  /** +   * Interpreter state. +   */ +  struct TALER_TESTING_Interpreter *is; + +  /** +   * Set to the wire transfer's unique ID. +   */ +  uint64_t serial_id; + +  /** +   * Timestamp of the transaction (as returned from the bank). +   */ +  struct GNUNET_TIME_Absolute timestamp; + +  /** +   * Merchant instance.  Sometimes used to get the tip reserve +   * private key by reading the appropriate config section. +   */ +  const char *instance; + +  /** +   * Configuration filename.  Used to get the tip reserve key +   * filename (used to obtain a public key to write in the +   * transfer subject). +   */ +  const char *config_filename; + +  /** +   * Task scheduled to try later. +   */ +  struct GNUNET_SCHEDULER_Task *retry_task; + +  /** +   * How long do we wait until we retry? +   */ +  struct GNUNET_TIME_Relative backoff; + +  /** +   * Was this command modified via +   * #TALER_TESTING_cmd_admin_add_incoming_with_retry to +   * enable retries? +   */ +  int do_retry; +}; + + +/** + * Run the "fakebank transfer" CMD. + * + * @param cls closure. + * @param cmd CMD being run. + * @param is interpreter state. + */ +static void +admin_add_incoming_run (void *cls, +                        const struct TALER_TESTING_Command *cmd, +                        struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #admin_add_incoming_run. + * + * @param cls a `struct AdminAddIncomingState` + */ +static void +do_retry (void *cls) +{ +  struct AdminAddIncomingState *fts = cls; + +  fts->retry_task = NULL; +  admin_add_incoming_run (fts, +                          NULL, +                          fts->is); +} + + +/** + * This callback will process the fakebank response to the wire + * transfer.  It just checks whether the HTTP response code is + * 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 + */ +static void +confirmation_cb (void *cls, +                 unsigned int http_status, +                 enum TALER_ErrorCode ec, +                 uint64_t serial_id, +                 struct GNUNET_TIME_Absolute timestamp, +                 const json_t *json) +{ +  struct AdminAddIncomingState *fts = cls; +  struct TALER_TESTING_Interpreter *is = fts->is; + +  fts->aih = NULL; +  if (MHD_HTTP_OK != http_status) +  { +    if (GNUNET_YES == fts->do_retry) +    { +      if ( (0 == http_status) || +           (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || +           (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) +      { +        GNUNET_log +          (GNUNET_ERROR_TYPE_INFO, +          "Retrying fakebank transfer failed with %u/%d\n", +          http_status, +          (int) ec); +        /* on DB conflicts, do not use backoff */ +        if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) +          fts->backoff = GNUNET_TIME_UNIT_ZERO; +        else +          fts->backoff = EXCHANGE_LIB_BACKOFF (fts->backoff); +        fts->retry_task = GNUNET_SCHEDULER_add_delayed +                            (fts->backoff, +                            &do_retry, +                            fts); +        return; +      } +    } +    GNUNET_break (0); +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Fakebank returned HTTP status %u/%d\n", +                http_status, +                (int) ec); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  fts->serial_id = serial_id; +  fts->timestamp = timestamp; +  TALER_TESTING_interpreter_next (is); +} + + +/** + * Run the "fakebank transfer" CMD. + * + * @param cls closure. + * @param cmd CMD being run. + * @param is interpreter state. + */ +static void +admin_add_incoming_run (void *cls, +                        const struct TALER_TESTING_Command *cmd, +                        struct TALER_TESTING_Interpreter *is) +{ +  struct AdminAddIncomingState *fts = cls; + +  /* Use reserve public key as subject */ +  if (NULL != fts->reserve_reference) +  { +    const struct TALER_TESTING_Command *ref; +    const struct TALER_ReservePrivateKeyP *reserve_priv; + +    ref = TALER_TESTING_interpreter_lookup_command +            (is, fts->reserve_reference); +    if (NULL == ref) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (is); +      return; +    } +    if (GNUNET_OK != +        TALER_TESTING_get_trait_reserve_priv (ref, +                                              0, +                                              &reserve_priv)) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (is); +      return; +    } +    fts->reserve_priv.eddsa_priv = reserve_priv->eddsa_priv; +  } +  else +  { +    if (NULL != fts->instance) +    { +      char *section; +      char *keys; +      struct GNUNET_CRYPTO_EddsaPrivateKey *priv; +      struct GNUNET_CONFIGURATION_Handle *cfg; + +      GNUNET_assert (NULL != fts->config_filename); +      cfg = GNUNET_CONFIGURATION_create (); +      if (GNUNET_OK != +          GNUNET_CONFIGURATION_load (cfg, +                                     fts->config_filename)) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } + +      GNUNET_asprintf (§ion, +                       "instance-%s", +                       fts->instance); +      if (GNUNET_OK != +          GNUNET_CONFIGURATION_get_value_filename +            (cfg, +            section, +            "TIP_RESERVE_PRIV_FILENAME", +            &keys)) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Configuration fails to specify reserve" +                    " private key filename in section %s\n", +                    section); +        GNUNET_free (section); +        TALER_TESTING_interpreter_fail (is); +        return; +      } +      priv = GNUNET_CRYPTO_eddsa_key_create_from_file (keys); +      GNUNET_free (keys); +      if (NULL == priv) +      { +        GNUNET_log_config_invalid +          (GNUNET_ERROR_TYPE_ERROR, +          section, +          "TIP_RESERVE_PRIV_FILENAME", +          "Failed to read private key"); +        GNUNET_free (section); +        TALER_TESTING_interpreter_fail (is); +        return; +      } +      fts->reserve_priv.eddsa_priv = *priv; +      GNUNET_free (section); +      GNUNET_free (priv); +      GNUNET_CONFIGURATION_destroy (cfg); +    } +    else +    { +      /* No referenced reserve, no instance to take priv +       * from, no explicit subject given: create new key! */ +      struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + +      priv = GNUNET_CRYPTO_eddsa_key_create (); +      fts->reserve_priv.eddsa_priv = *priv; +      GNUNET_free (priv); +    } +  } +  GNUNET_CRYPTO_eddsa_key_get_public (&fts->reserve_priv.eddsa_priv, +                                      &fts->reserve_pub.eddsa_pub); +  fts->is = is; +  fts->aih +    = TALER_BANK_admin_add_incoming +        (TALER_TESTING_interpreter_get_context (is), +        &fts->auth, +        &fts->reserve_pub, +        &fts->amount, +        fts->payto_debit_account, +        &confirmation_cb, +        fts); +  if (NULL == fts->aih) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +} + + +/** + * Free the state of a "/admin/add-incoming" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure + * @param cmd current CMD being cleaned up. + */ +static void +admin_add_incoming_cleanup (void *cls, +                            const struct TALER_TESTING_Command *cmd) +{ +  struct AdminAddIncomingState *fts = cls; + +  if (NULL != fts->aih) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Command %s did not complete\n", +                cmd->label); +    TALER_BANK_admin_add_incoming_cancel (fts->aih); +    fts->aih = NULL; +  } +  if (NULL != fts->retry_task) +  { +    GNUNET_SCHEDULER_cancel (fts->retry_task); +    fts->retry_task = NULL; +  } +  GNUNET_free (fts); +} + + +/** + * Offer internal data from a "/admin/add-incoming" 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 int +admin_add_incoming_traits (void *cls, +                           const void **ret, +                           const char *trait, +                           unsigned int index) +{ +  struct AdminAddIncomingState *fts = cls; +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_make_trait_bank_row (&fts->serial_id), +    TALER_TESTING_make_trait_payto (TALER_TESTING_PT_DEBIT, +                                    fts->payto_debit_account), +    /* Used as a marker, content does not matter */ +    TALER_TESTING_make_trait_payto (TALER_TESTING_PT_CREDIT, +                                    "payto://void/the-exchange"), +    TALER_TESTING_make_trait_url (TALER_TESTING_UT_EXCHANGE_BANK_ACCOUNT_URL, +                                  fts->exchange_credit_url), +    TALER_TESTING_make_trait_amount_obj (0, &fts->amount), +    TALER_TESTING_make_trait_absolute_time (0, &fts->timestamp), +    TALER_TESTING_make_trait_reserve_priv (0, +                                           &fts->reserve_priv), +    TALER_TESTING_make_trait_reserve_pub (0, +                                          &fts->reserve_pub), +    TALER_TESTING_trait_end () +  }; + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Create internal state for "/admin/add-incoming" CMD. + * + * @param amount the amount to transfer. + * @param payto_debit_account which account sends money + * @param auth authentication data + * @return the internal state + */ +static struct AdminAddIncomingState * +make_fts (const char *amount, +          const struct TALER_BANK_AuthenticationData *auth, +          const char *payto_debit_account) +{ +  struct AdminAddIncomingState *fts; + +  fts = GNUNET_new (struct AdminAddIncomingState); +  fts->exchange_credit_url = auth->wire_gateway_url; +  fts->payto_debit_account = payto_debit_account; +  fts->auth = *auth; +  if (GNUNET_OK != +      TALER_string_to_amount (amount, +                              &fts->amount)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to parse amount `%s'\n", +                amount); +    GNUNET_assert (0); +  } +  return fts; +} + + +/** + * Helper function to create admin/add-incoming command. + * + * @param label command label. + * @param fts internal state to use + * @return the command. + */ +static struct TALER_TESTING_Command +make_command (const char *label, +              struct AdminAddIncomingState *fts) +{ +  struct TALER_TESTING_Command cmd = { +    .cls = fts, +    .label = label, +    .run = &admin_add_incoming_run, +    .cleanup = &admin_add_incoming_cleanup, +    .traits = &admin_add_incoming_traits +  }; + +  return cmd; +} + + +/** + * Create admin/add-incoming command. + * + * @param label command label. + * @param amount amount to transfer. + * @param payto_debit_account which account sends money. + * @param auth authentication data + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_admin_add_incoming (const char *label, +                                      const char *amount, +                                      const struct +                                      TALER_BANK_AuthenticationData *auth, +                                      const char *payto_debit_account) +{ +  return make_command (label, +                       make_fts (amount, +                                 auth, +                                 payto_debit_account)); +} + + +/** + * Create "/admin/add-incoming" CMD, letting the caller specify + * a reference to a command that can offer a reserve private key. + * This private key will then be used to construct the subject line + * of the wire transfer. + * + * @param label command label. + * @param amount the amount to transfer. + * @param payto_debit_account which account sends money + * @param auth authentication data + * @param ref reference to a command that can offer a reserve + *        private key. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_admin_add_incoming_with_ref +  (const char *label, +  const char *amount, +  const struct TALER_BANK_AuthenticationData *auth, +  const char *payto_debit_account, +  const char *ref) +{ +  struct AdminAddIncomingState *fts; + +  fts = make_fts (amount, +                  auth, +                  payto_debit_account); +  fts->reserve_reference = ref; +  return make_command (label, +                       fts); +} + + +/** + * Create "/admin/add-incoming" CMD, letting the caller specifying + * the merchant instance.  This version is useful when a tip + * reserve should be topped up, in fact the interpreter will need + * the "tipping instance" in order to get the instance public key + * and make a wire transfer subject out of it. + * + * @param label command label. + * @param amount amount to transfer. + * @param payto_debit_account which account (expressed as a number) + *        gives money + * @param auth authentication data + * @param instance the instance that runs the tipping.  Under this + *        instance, the configuration file will provide the private + *        key of the tipping reserve.  This data will then used to + *        construct the wire transfer subject line. + * @param config_filename configuration file to use. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_admin_add_incoming_with_instance +  (const char *label, +  const char *amount, +  const struct TALER_BANK_AuthenticationData *auth, +  const char *payto_debit_account, +  const char *instance, +  const char *config_filename) +{ +  struct AdminAddIncomingState *fts; + +  fts = make_fts (amount, +                  auth, +                  payto_debit_account); +  fts->instance = instance; +  fts->config_filename = config_filename; + +  return make_command (label, +                       fts); +} + + +/** + * Modify a fakebank transfer command to enable retries when the + * reserve is not yet full or we get other transient errors from the + * fakebank. + * + * @param cmd a fakebank transfer command + * @return the command with retries enabled + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_admin_add_incoming_retry (struct TALER_TESTING_Command cmd) +{ +  struct AdminAddIncomingState *fts; + +  GNUNET_assert (&admin_add_incoming_run == cmd.run); +  fts = cmd.cls; +  fts->do_retry = GNUNET_YES; +  return cmd; +} + + +/* end of testing_api_cmd_bank_admin_add_incoming.c */ diff --git a/src/testing/testing_api_cmd_bank_admin_check.c b/src/testing/testing_api_cmd_bank_admin_check.c new file mode 100644 index 00000000..ced2a8c8 --- /dev/null +++ b/src/testing/testing_api_cmd_bank_admin_check.c @@ -0,0 +1,224 @@ +/* +  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_cmd_bank_admin_check.c + * @brief command to check if a particular admin/add-incoming transfer took + *        place. + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_testing_lib.h" +#include "taler_fakebank_lib.h" + + +/** + * State for a "bank check" CMD. + */ +struct BankAdminCheckState +{ + +  /** +   * Expected transferred amount. +   */ +  const char *amount; + +  /** +   * Expected debit bank account. +   */ +  const char *debit_payto; + +  /** +   * Expected credit bank account. +   */ +  const char *credit_payto; + +  /** +   * Command providing the reserve public key trait to use. +   */ +  const char *reserve_pub_ref; + +  /** +   * 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 +check_bank_admin_transfer_run (void *cls, +                               const struct TALER_TESTING_Command *cmd, +                               struct TALER_TESTING_Interpreter *is) +{ +  struct BankAdminCheckState *bcs = cls; +  struct TALER_Amount amount; +  char *debit_account; +  char *credit_account; +  const char *debit_payto; +  const char *credit_payto; +  const struct TALER_ReservePublicKeyP *reserve_pub; +  const struct TALER_TESTING_Command *cmd_ref; + +  cmd_ref +    = TALER_TESTING_interpreter_lookup_command (is, +                                                bcs->reserve_pub_ref); +  if (NULL == cmd_ref) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  if (GNUNET_OK != +      TALER_TESTING_get_trait_reserve_pub (cmd_ref, +                                           0, +                                           &reserve_pub)) +  { +    GNUNET_break (0); +    TALER_LOG_ERROR ("Command reference fails to provide reserve public key\n"); +    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 != +      TALER_string_to_amount (bcs->amount, +                              &amount)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to parse amount `%s' at %u\n", +                bcs->amount, +                is->ip); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  debit_account = TALER_xtalerbank_account_from_payto (debit_payto); +  credit_account = TALER_xtalerbank_account_from_payto (credit_payto); +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "converted debit_payto (%s) to debit_account (%s)\n", +              debit_payto, +              debit_account); +  if (GNUNET_OK != +      TALER_FAKEBANK_check_credit (is->fakebank, +                                   &amount, +                                   debit_account, +                                   credit_account, +                                   reserve_pub)) +  { +    GNUNET_break (0); +    GNUNET_free (credit_account); +    GNUNET_free (debit_account); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  GNUNET_free (credit_account); +  GNUNET_free (debit_account); +  TALER_TESTING_interpreter_next (is); +} + + +/** + * Free the state of a "bank check" CMD. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +check_bank_admin_transfer_cleanup (void *cls, +                                   const struct TALER_TESTING_Command *cmd) +{ +  struct BankAdminCheckState *bcs = cls; + +  GNUNET_free (bcs); +} + + +/** + * Offer internal data from a "bank admin check" CMD state. + * + * @param cls closure. + * @param[out] ret result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +check_bank_admin_transfer_traits (void *cls, +                                  const void **ret, +                                  const char *trait, +                                  unsigned int index) +{ +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_trait_end () +  }; + +  (void) cls; +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Make a "bank check" CMD.  It checks whether a particular wire transfer to + * the exchange (credit) has been made or not. + * + * @param label the command label. + * @param amount the amount expected to be transferred. + * @param debit_payto the account that gave money. + * @param credit_payto the account that received money. + * @param reserve_pub_ref command that provides the reserve public key to expect + * @return the command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_check_bank_admin_transfer +  (const char *label, +  const char *amount, +  const char *debit_payto, +  const char *credit_payto, +  const char *reserve_pub_ref) +{ +  struct BankAdminCheckState *bcs; + +  bcs = GNUNET_new (struct BankAdminCheckState); +  bcs->amount = amount; +  bcs->debit_payto = debit_payto; +  bcs->credit_payto = credit_payto; +  bcs->reserve_pub_ref = reserve_pub_ref; +  { +    struct TALER_TESTING_Command cmd = { +      .label = label, +      .cls = bcs, +      .run = &check_bank_admin_transfer_run, +      .cleanup = &check_bank_admin_transfer_cleanup, +      .traits = &check_bank_admin_transfer_traits +    }; + +    return cmd; +  } +} diff --git a/src/testing/testing_api_cmd_bank_check.c b/src/testing/testing_api_cmd_bank_check.c new file mode 100644 index 00000000..9d1f3e8e --- /dev/null +++ b/src/testing/testing_api_cmd_bank_check.c @@ -0,0 +1,305 @@ +/* +  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_cmd_bank_check.c + * @brief command to check if a particular wire transfer took + *        place. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_testing_lib.h" +#include "taler_fakebank_lib.h" + + +/** + * State for a "bank check" CMD. + */ +struct BankCheckState +{ + +  /** +   * Base URL of the exchange supposed to be +   * involved in the bank transaction. +   */ +  const char *exchange_base_url; + +  /** +   * Expected transferred amount. +   */ +  const char *amount; + +  /** +   * Expected debit bank account. +   */ +  const char *debit_payto; + +  /** +   * Expected credit bank account. +   */ +  const char *credit_payto; + +  /** +   * Binary form of the wire transfer subject. +   */ +  struct TALER_WireTransferIdentifierRawP wtid; + +  /** +   * Interpreter state. +   */ +  struct TALER_TESTING_Interpreter *is; + +  /** +   * Reference to a CMD that provides all the data +   * needed to issue the bank check.  If NULL, that data +   * must exist here in the state. +   */ +  const char *deposit_reference; +}; + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +check_bank_transfer_run (void *cls, +                         const struct TALER_TESTING_Command *cmd, +                         struct TALER_TESTING_Interpreter *is) +{ +  struct BankCheckState *bcs = cls; +  struct TALER_Amount amount; +  char *debit_account; +  char *credit_account; +  const char *exchange_base_url; +  const char *debit_payto; +  const char *credit_payto; + +  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; + +    if (GNUNET_OK != +        TALER_string_to_amount (bcs->amount, +                                &amount)) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Failed to parse amount `%s' at %u\n", +                  bcs->amount, +                  is->ip); +      TALER_TESTING_interpreter_fail (is); +      return; +    } +  } +  else +  { +    const struct TALER_TESTING_Command *deposit_cmd; +    const struct TALER_Amount *amount_ptr; + +    TALER_LOG_INFO ("`%s' uses reference (%s/%p)\n", +                    TALER_TESTING_interpreter_get_current_label +                      (is), +                    bcs->deposit_reference, +                    bcs->deposit_reference); +    deposit_cmd +      = TALER_TESTING_interpreter_lookup_command (is, +                                                  bcs->deposit_reference); +    if (NULL == deposit_cmd) +      TALER_TESTING_FAIL (is); +    if ( (GNUNET_OK != +          TALER_TESTING_get_trait_amount_obj (deposit_cmd, +                                              0, +                                              &amount_ptr)) || +         (GNUNET_OK != +          TALER_TESTING_get_trait_payto (deposit_cmd, +                                         TALER_TESTING_PT_DEBIT, +                                         &debit_payto)) || +         (GNUNET_OK != +          TALER_TESTING_get_trait_payto (deposit_cmd, +                                         TALER_TESTING_PT_CREDIT, +                                         &credit_payto)) || +         (GNUNET_OK != +          TALER_TESTING_get_trait_url (deposit_cmd, +                                       TALER_TESTING_UT_EXCHANGE_BASE_URL, +                                       &exchange_base_url)) ) +      TALER_TESTING_FAIL (is); +    amount = *amount_ptr; +  } + + +  debit_account = TALER_xtalerbank_account_from_payto (debit_payto); +  credit_account = TALER_xtalerbank_account_from_payto (credit_payto); + +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "converted debit_payto (%s) to debit_account (%s)\n", +              debit_payto, +              debit_account); + +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "converted credit_payto (%s) to credit_account (%s)\n", +              credit_payto, +              credit_account); + +  if (GNUNET_OK != +      TALER_FAKEBANK_check_debit (is->fakebank, +                                  &amount, +                                  debit_account, +                                  credit_account, +                                  exchange_base_url, +                                  &bcs->wtid)) +  { +    GNUNET_break (0); +    GNUNET_free (credit_account); +    GNUNET_free (debit_account); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  GNUNET_free (credit_account); +  GNUNET_free (debit_account); +  TALER_TESTING_interpreter_next (is); +} + + +/** + * Free the state of a "bank check" CMD. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +check_bank_transfer_cleanup (void *cls, +                             const struct TALER_TESTING_Command *cmd) +{ +  struct BankCheckState *bcs = cls; + +  GNUNET_free (bcs); +} + + +/** + * Offer internal data from a "bank check" CMD state. + * + * @param cls closure. + * @param[out] ret result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +check_bank_transfer_traits (void *cls, +                            const void **ret, +                            const char *trait, +                            unsigned int index) +{ +  struct BankCheckState *bcs = cls; +  struct TALER_WireTransferIdentifierRawP *wtid_ptr = &bcs->wtid; +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_make_trait_wtid (0, +                                   wtid_ptr), +    TALER_TESTING_make_trait_url (TALER_TESTING_UT_EXCHANGE_BASE_URL, +                                  bcs->exchange_base_url), +    TALER_TESTING_trait_end () +  }; + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * 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, +                                       const char *amount, +                                       const char *debit_payto, +                                       const char *credit_payto) +{ +  struct BankCheckState *bcs; + +  bcs = GNUNET_new (struct BankCheckState); +  bcs->exchange_base_url = exchange_base_url; +  bcs->amount = amount; +  bcs->debit_payto = debit_payto; +  bcs->credit_payto = credit_payto; +  bcs->deposit_reference = NULL; +  { +    struct TALER_TESTING_Command cmd = { +      .label = label, +      .cls = bcs, +      .run = &check_bank_transfer_run, +      .cleanup = &check_bank_transfer_cleanup, +      .traits = &check_bank_transfer_traits +    }; + +    return cmd; +  } +} + + +/** + * 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, +  const char *deposit_reference) +{ +  struct BankCheckState *bcs; + +  bcs = GNUNET_new (struct BankCheckState); +  bcs->deposit_reference = deposit_reference; +  { +    struct TALER_TESTING_Command cmd = { +      .label = label, +      .cls = bcs, +      .run = &check_bank_transfer_run, +      .cleanup = &check_bank_transfer_cleanup, +      .traits = &check_bank_transfer_traits +    }; + +    return cmd; +  } +} diff --git a/src/testing/testing_api_cmd_bank_check_empty.c b/src/testing/testing_api_cmd_bank_check_empty.c new file mode 100644 index 00000000..a26fd9bb --- /dev/null +++ b/src/testing/testing_api_cmd_bank_check_empty.c @@ -0,0 +1,102 @@ +/* +  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_cmd_bank_check_empty.c + * @brief command to check if a particular wire transfer took + *        place. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_testing_lib.h" +#include "taler_fakebank_lib.h" + + +/** + * Cleanup the state, only defined to respect the API. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +check_bank_empty_cleanup +  (void *cls, +  const struct TALER_TESTING_Command *cmd) +{ +  return; +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +check_bank_empty_run (void *cls, +                      const struct TALER_TESTING_Command *cmd, +                      struct TALER_TESTING_Interpreter *is) +{ +  if (GNUNET_OK != TALER_FAKEBANK_check_empty (is->fakebank)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  TALER_TESTING_interpreter_next (is); +} + + +/** + * Some commands (notably "bank history") could randomly + * look for traits; this way makes sure we don't segfault. + */ +static int +check_bank_empty_traits (void *cls, +                         const void **ret, +                         const char *trait, +                         unsigned int index) +{ +  return GNUNET_SYSERR; +} + + +/** + * Checks wheter all the wire transfers got "checked" + * by the "bank check" CMD. + * + * @param label command label. + * + * @return the command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_check_bank_empty (const char *label) +{ +  struct TALER_TESTING_Command cmd = { +    .label = label, +    .run = &check_bank_empty_run, +    .cleanup = &check_bank_empty_cleanup, +    .traits = &check_bank_empty_traits +  }; + +  return cmd; +} diff --git a/src/testing/testing_api_cmd_bank_history_credit.c b/src/testing/testing_api_cmd_bank_history_credit.c new file mode 100644 index 00000000..8fb16fc6 --- /dev/null +++ b/src/testing/testing_api_cmd_bank_history_credit.c @@ -0,0 +1,599 @@ +/* +  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_cmd_bank_history_credit.c + * @brief command to check the /history/incoming API from the bank. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_testing_lib.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" + + +/** + * Item in the transaction history, as reconstructed from the + * command history. + */ +struct History +{ + +  /** +   * Wire details. +   */ +  struct TALER_BANK_CreditDetails details; + +  /** +   * Serial ID of the wire transfer. +   */ +  uint64_t row_id; + +  /** +   * URL to free. +   */ +  char *url; +}; + + +/** + * State for a "history" CMD. + */ +struct HistoryState +{ +  /** +   * Base URL of the account offering the "history" operation. +   */ +  char *account_url; + +  /** +   * Reference to command defining the +   * first row number we want in the result. +   */ +  const char *start_row_reference; + +  /** +   * How many rows we want in the result, _at most_, +   * and ascending/descending. +   */ +  long long num_results; + +  /** +   * Handle to a pending "history" operation. +   */ +  struct TALER_BANK_CreditHistoryHandle *hh; + +  /** +   * Authentication data for the operation. +   */ +  struct TALER_BANK_AuthenticationData auth; + +  /** +   * Expected number of results (= rows). +   */ +  uint64_t results_obtained; + +  /** +   * Set to GNUNET_YES if the callback detects something +   * unexpected. +   */ +  int failed; + +  /** +   * Expected history. +   */ +  struct History *h; + +  /** +   * Length of @e h +   */ +  unsigned int total; + +}; + + +/** + * Offer internal data to other commands. + * + * @param cls closure. + * @param[out] ret set to the wanted data. + * @param trait name of the trait. + * @param index index number of the traits to be returned. + * + * @return #GNUNET_OK on success + */ +static int +history_traits (void *cls, +                const void **ret, +                const char *trait, +                unsigned int index) +{ +  (void) cls; +  (void) ret; +  (void) trait; +  (void) index; +  /* Must define this function because some callbacks +   * look for certain traits on _all_ the commands. */ +  return GNUNET_SYSERR; +} + + +/** + * Log which history we expected.  Called when an error occurs. + * + * @param h what we expected. + * @param h_len number of entries in @a h. + * @param off position of the missmatch. + */ +static void +print_expected (struct History *h, +                unsigned int h_len, +                unsigned int off) +{ +  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +              "Transaction history (credit) mismatch at position %u/%u\n", +              off, +              h_len); +  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +              "Expected history:\n"); +  for (unsigned int i = 0; i<h_len; i++) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "H(%u): %s (serial: %llu, subject: %s," +                " counterpart: %s)\n", +                i, +                TALER_amount2s (&h[i].details.amount), +                (unsigned long long) h[i].row_id, +                TALER_B2S (&h[i].details.reserve_pub), +                h[i].details.debit_account_url); +  } +} + + +/** + * 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[out] rh history array to initialize. + * @return number of entries in @a rh. + */ +static unsigned int +build_history (struct TALER_TESTING_Interpreter *is, +               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; + +  if (NULL != hs->start_row_reference) +  { +    TALER_LOG_INFO ("`%s': start row given via reference `%s'\n", +                    TALER_TESTING_interpreter_get_current_label (is), +                    hs->start_row_reference); +    add_incoming_cmd +      = TALER_TESTING_interpreter_lookup_command (is, +                                                  hs->start_row_reference); +    GNUNET_assert (NULL != add_incoming_cmd); +    GNUNET_assert (GNUNET_OK == +                   TALER_TESTING_get_trait_uint64 (add_incoming_cmd, +                                                   0, +                                                   &row_id_start)); +  } + +  GNUNET_assert (0 != hs->num_results); +  if (0 == is->ip) +  { +    TALER_LOG_DEBUG ("Checking history at FIRST transaction (EMPTY)\n"); +    *rh = NULL; +    return 0; +  } + +  if (hs->num_results > 0) +  { +    inc = 1;  /* _inc_rement */ +    start = 0; +    end = is->ip - 1; +  } +  else +  { +    inc = -1; +    start = is->ip - 1; +    end = 0; +  } + +  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) +  { +    const struct TALER_TESTING_Command *cmd = &is->commands[off]; +    const uint64_t *row_id; +    const char *credit_account; +    const char *debit_account; +    const struct TALER_Amount *amount; +    const struct TALER_ReservePublicKeyP *reserve_pub; +    const char *exchange_credit_url; + +    /* The following command allows us to skip over those CMDs +     * that do not offer a "row_id" trait.  Such skipped CMDs are +     * not interesting for building a history. */// +    if ( (GNUNET_OK != +          TALER_TESTING_get_trait_bank_row (cmd, +                                            &row_id)) || +         (GNUNET_OK != +          TALER_TESTING_get_trait_payto (cmd, +                                         TALER_TESTING_PT_CREDIT, +                                         &credit_account)) || +         (GNUNET_OK != +          TALER_TESTING_get_trait_payto (cmd, +                                         TALER_TESTING_PT_DEBIT, +                                         &debit_account)) || +         (GNUNET_OK != +          TALER_TESTING_get_trait_amount_obj (cmd, +                                              0, +                                              &amount)) || +         (GNUNET_OK != +          TALER_TESTING_get_trait_reserve_pub (cmd, +                                               0, +                                               &reserve_pub)) || +         (GNUNET_OK != +          TALER_TESTING_get_trait_url (cmd, +                                       TALER_TESTING_UT_EXCHANGE_BANK_ACCOUNT_URL, +                                       &exchange_credit_url)) ) +      continue; /* not an interesting event */ +    /* Seek "/history/incoming" starting row.  */ +    if ( (NULL != row_id_start) && +         (*row_id_start == *row_id) && +         (GNUNET_NO == ok) ) +    { +      /* Until here, nothing counted. */ +      ok = GNUNET_YES; +      continue; +    } +    /* when 'start' was _not_ given, then ok == GNUNET_YES */ +    if (GNUNET_NO == ok) +      continue; /* skip until we find the marker */ +    if (0 != strcasecmp (hs->account_url, +                         exchange_credit_url)) +      continue; /* account missmatch */ +    if (total >= GNUNET_MAX (hs->num_results, +                             -hs->num_results) ) +    { +      TALER_LOG_DEBUG ("Hit history limit\n"); +      break; +    } +    TALER_LOG_INFO ("Found history: %s->%s for account %s\n", +                    debit_account, +                    credit_account, +                    hs->account_url); +    /* found matching record, make sure we have room */ +    if (pos == total) +      GNUNET_array_grow (h, +                         total, +                         pos * 2); +    h[pos].url = GNUNET_strdup (debit_account); +    h[pos].details.debit_account_url = h[pos].url; +    h[pos].details.amount = *amount; +    h[pos].row_id = *row_id; +    h[pos].details.reserve_pub = *reserve_pub; +    h[pos].details.credit_account_url = exchange_credit_url; +    pos++; +  } +  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; +} + + +/** + * Check that the "/history/incoming" response matches the + * CMD whose offset in the list of CMDs is @a off. + * + * @param is the interpreter state. + * @param h expected history (array) + * @param total length of @a h + * @param off the offset (of the CMD list) where the command + *        to check is. + * @param details the expected transaction details. + * @return #GNUNET_OK if the transaction is what we expect. + */ +static int +check_result (struct TALER_TESTING_Interpreter *is, +              struct History *h, +              unsigned int total, +              unsigned int off, +              const struct TALER_BANK_CreditDetails *details) +{ +  if (off >= total) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Test says history has at most %u" +                " results, but got result #%u to check\n", +                total, +                off); +    print_expected (h, +                    total, +                    off); +    return GNUNET_SYSERR; +  } +  if ( (0 != GNUNET_memcmp (&h[off].details.reserve_pub, +                            &details->reserve_pub)) || +       (0 != TALER_amount_cmp (&h[off].details.amount, +                               &details->amount)) || +       (0 != strcasecmp (h[off].details.debit_account_url, +                         details->debit_account_url)) ) +  { +    GNUNET_break (0); +    GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                "expected debit_account_url: %s\n", +                details->debit_account_url); +    GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                "actual debit_account_url: %s\n", +                h[off].details.debit_account_url); +    print_expected (h, +                    total, +                    off); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * This callback will (1) check that the HTTP response code + * is acceptable and (2) that the history is consistent.  The + * consistency is checked by going through all the past CMDs, + * reconstructing then the expected history as of those, and + * 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 + */ +static int +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) +{ +  struct TALER_TESTING_Interpreter *is = cls; +  struct HistoryState *hs = is->commands[is->ip].cls; + +  (void) row_id; +  if (NULL == details) +  { +    hs->hh = NULL; +    if ( (hs->results_obtained != hs->total) || +         (GNUNET_YES == hs->failed) || +         (MHD_HTTP_NO_CONTENT != http_status) ) +    { +      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; +    } +    TALER_TESTING_interpreter_next (is); +    return GNUNET_OK; +  } +  if (MHD_HTTP_OK != http_status) +  { +    hs->hh = NULL; +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unwanted response code from /history/incoming: %u\n", +                http_status); +    TALER_TESTING_interpreter_fail (is); +    return GNUNET_SYSERR; +  } + +  /* check current element */ +  if (GNUNET_OK != check_result (is, +                                 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; +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +history_run (void *cls, +             const struct TALER_TESTING_Command *cmd, +             struct TALER_TESTING_Interpreter *is) +{ +  struct HistoryState *hs = cls; +  uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX; +  const uint64_t *row_ptr; + +  (void) cmd; +  /* Get row_id from trait. */ +  if (NULL != hs->start_row_reference) +  { +    const struct TALER_TESTING_Command *history_cmd; + +    history_cmd = TALER_TESTING_interpreter_lookup_command +                    (is, hs->start_row_reference); + +    if (NULL == history_cmd) +      TALER_TESTING_FAIL (is); + +    if (GNUNET_OK != +        TALER_TESTING_get_trait_uint64 (history_cmd, +                                        0, +                                        &row_ptr)) +      TALER_TESTING_FAIL (is); +    else +      row_id = *row_ptr; +    TALER_LOG_DEBUG ("row id (from trait) is %llu\n", +                     (unsigned long long) row_id); +  } +  hs->total = build_history (is, +                             &hs->h); +  hs->hh = TALER_BANK_credit_history (is->ctx, +                                      &hs->auth, +                                      row_id, +                                      hs->num_results, +                                      &history_cb, +                                      is); +  GNUNET_assert (NULL != hs->hh); +} + + +/** + * Free the state from a "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 *hs = cls; + +  (void) cmd; +  if (NULL != hs->hh) +  { +    TALER_LOG_WARNING ("/history/incoming did not complete\n"); +    TALER_BANK_credit_history_cancel (hs->hh); +  } +  GNUNET_free (hs->account_url); +  for (unsigned int off = 0; off<hs->total; off++) +    GNUNET_free (hs->h[off].url); +  GNUNET_free_non_null (hs->h); +  GNUNET_free (hs); +} + + +/** + * Make a "history" CMD. + * + * @param label command label. + * @param auth authentication data to talk with the wire gateway + * @param start_row_reference reference to a command that can + *        offer a row identifier, to be used as the starting row + *        to accept in the result. + * @param num_results how many rows we want in the result. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_bank_credits (const char *label, +                                const struct +                                TALER_BANK_AuthenticationData *auth, +                                const char *start_row_reference, +                                long long num_results) +{ +  struct HistoryState *hs; + +  hs = GNUNET_new (struct HistoryState); +  hs->account_url = GNUNET_strdup (auth->wire_gateway_url); +  hs->start_row_reference = start_row_reference; +  hs->num_results = num_results; +  hs->auth = *auth; +  { +    struct TALER_TESTING_Command cmd = { +      .label = label, +      .cls = hs, +      .run = &history_run, +      .cleanup = &history_cleanup, +      .traits = &history_traits +    }; + +    return cmd; +  } +} + + +/* end of testing_api_cmd_credit_history.c */ diff --git a/src/testing/testing_api_cmd_bank_history_debit.c b/src/testing/testing_api_cmd_bank_history_debit.c new file mode 100644 index 00000000..dea6bee9 --- /dev/null +++ b/src/testing/testing_api_cmd_bank_history_debit.c @@ -0,0 +1,602 @@ +/* +  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_cmd_bank_history_debit.c + * @brief command to check the /history/outgoing API from the bank. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_exchange_service.h" +#include "taler_testing_lib.h" +#include "taler_fakebank_lib.h" +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" + +/** + * Item in the transaction history, as reconstructed from the + * command history. + */ +struct History +{ + +  /** +   * Wire details. +   */ +  struct TALER_BANK_DebitDetails details; + +  /** +   * Serial ID of the wire transfer. +   */ +  uint64_t row_id; + +  /** +   * URL to free. +   */ +  char *c_url; + +  /** +   * URL to free. +   */ +  char *d_url; +}; + + +/** + * State for a "history" CMD. + */ +struct HistoryState +{ +  /** +   * Base URL of the account offering the "history" operation. +   */ +  const char *account_url; + +  /** +   * Reference to command defining the +   * first row number we want in the result. +   */ +  const char *start_row_reference; + +  /** +   * How many rows we want in the result, _at most_, +   * and ascending/descending. +   */ +  long long num_results; + +  /** +   * Login data to use to authenticate. +   */ +  struct TALER_BANK_AuthenticationData auth; + +  /** +   * Handle to a pending "history" operation. +   */ +  struct TALER_BANK_DebitHistoryHandle *hh; + +  /** +   * Expected number of results (= rows). +   */ +  uint64_t results_obtained; + +  /** +   * Set to #GNUNET_YES if the callback detects something +   * unexpected. +   */ +  int failed; + +  /** +   * Expected history. +   */ +  struct History *h; + +  /** +   * Length of @e h +   */ +  unsigned int total; + +}; + + +/** + * Offer internal data to other commands. + * + * @param cls closure. + * @param[out] ret set to the wanted data. + * @param trait name of the trait. + * @param index index number of the traits to be returned. + * + * @return #GNUNET_OK on success + */ +static int +history_traits (void *cls, +                const void **ret, +                const char *trait, +                unsigned int index) +{ +  (void) cls; +  (void) ret; +  (void) trait; +  (void) index; +  /* Must define this function because some callbacks +   * look for certain traits on _all_ the commands. */ +  return GNUNET_SYSERR; +} + + +/** + * Log which history we expected.  Called when an error occurs. + * + * @param h what we expected. + * @param h_len number of entries in @a h. + * @param off position of the missmatch. + */ +static void +print_expected (struct History *h, +                unsigned int h_len, +                unsigned int off) +{ +  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +              "Transaction history (debit) mismatch at position %u/%u\n", +              off, +              h_len); +  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +              "Expected history:\n"); +  for (unsigned int i = 0; i<h_len; i++) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "H(%u): %s (serial: %llu, subject: %s, counterpart: %s)\n", +                i, +                TALER_amount2s (&h[i].details.amount), +                (unsigned long long) h[i].row_id, +                TALER_B2S (&h[i].details.wtid), +                h[i].details.credit_account_url); +  } +} + + +/** + * 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[out] rh history array to initialize. + * @return number of entries in @a rh. + */ +static unsigned int +build_history (struct TALER_TESTING_Interpreter *is, +               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; + +  if (NULL != hs->start_row_reference) +  { +    TALER_LOG_INFO +      ("`%s': start row given via reference `%s'\n", +      TALER_TESTING_interpreter_get_current_label  (is), +      hs->start_row_reference); +    add_incoming_cmd = TALER_TESTING_interpreter_lookup_command +                         (is, hs->start_row_reference); +    GNUNET_assert (NULL != add_incoming_cmd); +    GNUNET_assert (GNUNET_OK == +                   TALER_TESTING_get_trait_uint64 (add_incoming_cmd, +                                                   0, +                                                   &row_id_start)); +  } + +  GNUNET_assert (0 != hs->num_results); +  if (0 == is->ip) +  { +    TALER_LOG_DEBUG ("Checking history at first CMD..\n"); +    *rh = NULL; +    return 0; +  } + +  /* AKA 'delta' */ +  if (hs->num_results > 0) +  { +    inc = 1;  /* _inc_rement: go forwards */ +    start = 0; +    end = is->ip; +  } +  else +  { +    inc = -1; /* decrement: we go backwards */ +    start = is->ip - 1; +    end = -1; /* range is exclusive, do look at 0! */ +  } + +  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) +  { +    const struct TALER_TESTING_Command *cmd = &is->commands[off]; +    const uint64_t *row_id; +    const char *debit_account; +    const char *credit_account; +    const struct TALER_Amount *amount; +    const struct TALER_WireTransferIdentifierRawP *wtid; +    const char *exchange_base_url; + +    GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                "Checking if command %s is relevant for debit history\n", +                cmd->label); +    if ( (GNUNET_OK != +          TALER_TESTING_get_trait_bank_row (cmd, +                                            &row_id)) || +         (GNUNET_OK != +          TALER_TESTING_get_trait_payto (cmd, +                                         TALER_TESTING_PT_DEBIT, +                                         &debit_account)) || +         (GNUNET_OK != +          TALER_TESTING_get_trait_payto (cmd, +                                         TALER_TESTING_PT_CREDIT, +                                         &credit_account)) || +         (GNUNET_OK != +          TALER_TESTING_get_trait_amount_obj (cmd, +                                              0, +                                              &amount)) || +         (GNUNET_OK != +          TALER_TESTING_get_trait_wtid (cmd, +                                        0, +                                        &wtid)) || +         (GNUNET_OK != +          TALER_TESTING_get_trait_url (cmd, +                                       TALER_TESTING_UT_EXCHANGE_BASE_URL, +                                       &exchange_base_url)) ) +      continue; /* not an event we care about */ +    /* Seek "/history/outgoing" starting row.  */ +    GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                "Command %s is relevant for debit history!\n", +                cmd->label); +    if ( (NULL != row_id_start) && +         (*row_id_start == *row_id) && +         (GNUNET_NO == ok) ) +    { +      /* Until here, nothing counted. */ +      ok = GNUNET_YES; +      continue; +    } +    /* when 'start' was _not_ given, then ok == GNUNET_YES */ +    if (GNUNET_NO == ok) +      continue; /* skip until we find the marker */ +    if (total >= GNUNET_MAX (hs->num_results, +                             -hs->num_results) ) +    { +      TALER_LOG_DEBUG ("Hit history limit\n"); +      break; +    } +    TALER_LOG_INFO ("Found history: %s->%s for account %s\n", +                    debit_account, +                    credit_account, +                    hs->account_url); +    /* found matching record, make sure we have room */ +    if (pos == total) +      GNUNET_array_grow (h, +                         total, +                         pos * 2); +    h[pos].c_url = GNUNET_strdup (credit_account); +    h[pos].d_url = GNUNET_strdup (debit_account); +    h[pos].details.credit_account_url = h[pos].c_url; +    h[pos].details.debit_account_url = h[pos].d_url; +    h[pos].details.amount = *amount; +    h[pos].row_id = *row_id; +    h[pos].details.wtid = *wtid; +    h[pos].details.exchange_base_url = exchange_base_url; +    pos++; +  } +  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; +} + + +/** + * Check that the "/history/outgoing" response matches the + * CMD whose offset in the list of CMDs is @a off. + * + * @param is the interpreter state. + * @param h expected history + * @param total number of entries in @a h + * @param off the offset (of the CMD list) where the command + *        to check is. + * @param details the expected transaction details. + * @return #GNUNET_OK if the transaction is what we expect. + */ +static int +check_result (struct TALER_TESTING_Interpreter *is, +              struct History *h, +              uint64_t total, +              unsigned int off, +              const struct TALER_BANK_DebitDetails *details) +{ +  if (off >= total) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Test says history has at most %u" +                " results, but got result #%u to check\n", +                (unsigned int) total, +                off); +    print_expected (h, +                    total, +                    off); +    return GNUNET_SYSERR; +  } +  if ( (0 != GNUNET_memcmp (&h[off].details.wtid, +                            &details->wtid)) || +       (0 != TALER_amount_cmp (&h[off].details.amount, +                               &details->amount)) || +       (0 != strcasecmp (h[off].details.credit_account_url, +                         details->credit_account_url)) ) +  { +    GNUNET_break (0); +    print_expected (h, +                    total, +                    off); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * This callback will (1) check that the HTTP response code + * is acceptable and (2) that the history is consistent.  The + * consistency is checked by going through all the past CMDs, + * reconstructing then the expected history as of those, and + * 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 + */ +static int +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) +{ +  struct TALER_TESTING_Interpreter *is = cls; +  struct HistoryState *hs = is->commands[is->ip].cls; + +  (void) row_id; +  if (NULL == details) +  { +    hs->hh = NULL; +    if ( (hs->results_obtained != hs->total) || +         (GNUNET_YES == hs->failed) || +         (MHD_HTTP_NO_CONTENT != http_status) ) +    { +      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; +    } +    TALER_TESTING_interpreter_next (is); +    return GNUNET_OK; +  } +  if (MHD_HTTP_OK != http_status) +  { +    hs->hh = NULL; +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unwanted response code from /history/outgoing: %u\n", +                http_status); +    TALER_TESTING_interpreter_fail (is); +    return GNUNET_SYSERR; +  } + +  /* check current element */ +  if (GNUNET_OK != check_result (is, +                                 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; +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +history_run (void *cls, +             const struct TALER_TESTING_Command *cmd, +             struct TALER_TESTING_Interpreter *is) +{ +  struct HistoryState *hs = cls; +  uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX; +  const uint64_t *row_ptr; + +  (void) cmd; +  /* Get row_id from trait. */ +  if (NULL != hs->start_row_reference) +  { +    const struct TALER_TESTING_Command *history_cmd; + +    history_cmd +      = TALER_TESTING_interpreter_lookup_command (is, +                                                  hs->start_row_reference); + +    if (NULL == history_cmd) +      TALER_TESTING_FAIL (is); +    if (GNUNET_OK != +        TALER_TESTING_get_trait_uint64 (history_cmd, +                                        0, +                                        &row_ptr)) +      TALER_TESTING_FAIL (is); +    else +      row_id = *row_ptr; +    TALER_LOG_DEBUG ("row id (from trait) is %llu\n", +                     (unsigned long long) row_id); +  } +  hs->total = build_history (is, &hs->h); +  hs->hh = TALER_BANK_debit_history (is->ctx, +                                     &hs->auth, +                                     row_id, +                                     hs->num_results, +                                     &history_cb, +                                     is); +  GNUNET_assert (NULL != hs->hh); +} + + +/** + * Free the state from a "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 *hs = cls; + +  (void) cmd; +  if (NULL != hs->hh) +  { +    TALER_LOG_WARNING ("/history/outgoing did not complete\n"); +    TALER_BANK_debit_history_cancel (hs->hh); +  } +  for (unsigned int off = 0; off<hs->total; off++) +  { +    GNUNET_free (hs->h[off].c_url); +    GNUNET_free (hs->h[off].d_url); +  } +  GNUNET_free_non_null (hs->h); +  GNUNET_free (hs); +} + + +/** + * Make a "history" CMD. + * + * @param label command label. + * @param auth login data to use + * @param start_row_reference reference to a command that can + *        offer a row identifier, to be used as the starting row + *        to accept in the result. + * @param num_results how many rows we want in the result. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_bank_debits (const char *label, +                               const struct TALER_BANK_AuthenticationData *auth, +                               const char *start_row_reference, +                               long long num_results) +{ +  struct HistoryState *hs; + +  hs = GNUNET_new (struct HistoryState); +  hs->account_url = auth->wire_gateway_url; +  hs->start_row_reference = start_row_reference; +  hs->num_results = num_results; +  hs->auth = *auth; + +  { +    struct TALER_TESTING_Command cmd = { +      .label = label, +      .cls = hs, +      .run = &history_run, +      .cleanup = &history_cleanup, +      .traits = &history_traits +    }; + +    return cmd; +  } +} + + +/* end of testing_api_cmd_bank_history_debit.c */ diff --git a/src/testing/testing_api_cmd_bank_transfer.c b/src/testing/testing_api_cmd_bank_transfer.c new file mode 100644 index 00000000..6aa926df --- /dev/null +++ b/src/testing/testing_api_cmd_bank_transfer.c @@ -0,0 +1,405 @@ +/* +  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_cmd_bank_transfer.c + * @brief implementation of a bank /transfer command + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "backoff.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_bank_service.h" +#include "taler_fakebank_lib.h" +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "transfer" CMD. + */ +struct TransferState +{ + +  /** +   * Wire transfer amount. +   */ +  struct TALER_Amount amount; + +  /** +   * Base URL of the debit account. +   */ +  const char *account_debit_url; + +  /** +   * Money receiver payto URL. +   */ +  char *payto_debit_account; + +  /** +   * Money receiver account URL. +   */ +  const char *payto_credit_account; + +  /** +   * Username to use for authentication. +   */ +  struct TALER_BANK_AuthenticationData auth; + +  /** +   * Base URL of the exchange. +   */ +  const char *exchange_base_url; + +  /** +   * Wire transfer identifier to use. +   */ +  struct TALER_WireTransferIdentifierRawP wtid; + +  /** +   * Handle to the pending request at the fakebank. +   */ +  struct TALER_BANK_WireExecuteHandle *weh; + +  /** +   * Interpreter state. +   */ +  struct TALER_TESTING_Interpreter *is; + +  /** +   * Set to the wire transfer's unique ID. +   */ +  uint64_t serial_id; + +  /** +   * Timestamp of the transaction (as returned from the bank). +   */ +  struct GNUNET_TIME_Absolute timestamp; + +  /** +   * Configuration filename.  Used to get the tip reserve key +   * filename (used to obtain a public key to write in the +   * transfer subject). +   */ +  const char *config_filename; + +  /** +   * Task scheduled to try later. +   */ +  struct GNUNET_SCHEDULER_Task *retry_task; + +  /** +   * How long do we wait until we retry? +   */ +  struct GNUNET_TIME_Relative backoff; + +  /** +   * Was this command modified via +   * #TALER_TESTING_cmd_admin_add_incoming_with_retry to +   * enable retries? +   */ +  int do_retry; +}; + + +/** + * Run the "transfer" CMD. + * + * @param cls closure. + * @param cmd CMD being run. + * @param is interpreter state. + */ +static void +transfer_run (void *cls, +              const struct TALER_TESTING_Command *cmd, +              struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #transfer_run. + * + * @param cls a `struct TransferState` + */ +static void +do_retry (void *cls) +{ +  struct TransferState *fts = cls; + +  fts->retry_task = NULL; +  transfer_run (fts, +                NULL, +                fts->is); +} + + +/** + * This callback will process the fakebank response to the wire + * transfer.  It just checks whether the HTTP response code is + * 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. + */ +static void +confirmation_cb (void *cls, +                 unsigned int http_status, +                 enum TALER_ErrorCode ec, +                 uint64_t serial_id, +                 struct GNUNET_TIME_Absolute timestamp) +{ +  struct TransferState *fts = cls; +  struct TALER_TESTING_Interpreter *is = fts->is; + +  fts->weh = NULL; +  if (MHD_HTTP_OK != http_status) +  { +    if (GNUNET_YES == fts->do_retry) +    { +      if ( (0 == http_status) || +           (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || +           (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                    "Retrying transfer failed with %u/%d\n", +                    http_status, +                    (int) ec); +        /* on DB conflicts, do not use backoff */ +        if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) +          fts->backoff = GNUNET_TIME_UNIT_ZERO; +        else +          fts->backoff = EXCHANGE_LIB_BACKOFF (fts->backoff); +        fts->retry_task = GNUNET_SCHEDULER_add_delayed +                            (fts->backoff, +                            &do_retry, +                            fts); +        return; +      } +    } +    GNUNET_break (0); +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Fakebank returned HTTP status %u/%d\n", +                http_status, +                (int) ec); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  fts->serial_id = serial_id; +  fts->timestamp = timestamp; +  TALER_TESTING_interpreter_next (is); +} + + +/** + * Run the "transfer" CMD. + * + * @param cls closure. + * @param cmd CMD being run. + * @param is interpreter state. + */ +static void +transfer_run (void *cls, +              const struct TALER_TESTING_Command *cmd, +              struct TALER_TESTING_Interpreter *is) +{ +  struct TransferState *fts = cls; +  void *buf; +  size_t buf_size; + +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "Transfer of %s from %s to %s\n", +              TALER_amount2s (&fts->amount), +              fts->account_debit_url, +              fts->payto_credit_account); +  TALER_BANK_prepare_wire_transfer (fts->payto_credit_account, +                                    &fts->amount, +                                    fts->exchange_base_url, +                                    &fts->wtid, +                                    &buf, +                                    &buf_size); +  fts->is = is; +  fts->weh +    = TALER_BANK_execute_wire_transfer +        (TALER_TESTING_interpreter_get_context (is), +        &fts->auth, +        buf, +        buf_size, +        &confirmation_cb, +        fts); +  GNUNET_free (buf); +  if (NULL == fts->weh) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +} + + +/** + * Free the state of a "fakebank transfer" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure + * @param cmd current CMD being cleaned up. + */ +static void +transfer_cleanup (void *cls, +                  const struct TALER_TESTING_Command *cmd) +{ +  struct TransferState *fts = cls; + +  if (NULL != fts->weh) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Command %s did not complete\n", +                cmd->label); +    TALER_BANK_execute_wire_transfer_cancel (fts->weh); +    fts->weh = NULL; +  } +  if (NULL != fts->retry_task) +  { +    GNUNET_SCHEDULER_cancel (fts->retry_task); +    fts->retry_task = NULL; +  } +  GNUNET_free (fts->payto_debit_account); +  GNUNET_free (fts); +} + + +/** + * Offer internal data from a "fakebank transfer" 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 int +transfer_traits (void *cls, +                 const void **ret, +                 const char *trait, +                 unsigned int index) +{ +  struct TransferState *fts = cls; +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_make_trait_url (TALER_TESTING_UT_EXCHANGE_BASE_URL, +                                  fts->exchange_base_url), +    TALER_TESTING_make_trait_bank_row (&fts->serial_id), +    TALER_TESTING_make_trait_payto (TALER_TESTING_PT_CREDIT, +                                    fts->payto_credit_account), +    TALER_TESTING_make_trait_payto (TALER_TESTING_PT_DEBIT, +                                    fts->payto_debit_account), +    TALER_TESTING_make_trait_amount_obj (0, &fts->amount), +    TALER_TESTING_make_trait_absolute_time (0, &fts->timestamp), +    TALER_TESTING_make_trait_wtid (0, +                                   &fts->wtid), +    TALER_TESTING_trait_end () +  }; + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Create transfer command. + * + * @param label command label. + * @param amount amount to transfer. + * @param auth authentication data to use + * @param payto_debit_account which account sends money. + * @param payto_credit_account which account receives money. + * @param wtid wire transfer identifier to use + * @param exchange_base_url exchange URL to use + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_transfer (const char *label, +                            const char *amount, +                            const struct TALER_BANK_AuthenticationData *auth, +                            const char *payto_debit_account, +                            const char *payto_credit_account, +                            const struct TALER_WireTransferIdentifierRawP *wtid, +                            const char *exchange_base_url) +{ +  struct TransferState *fts; + +  fts = GNUNET_new (struct TransferState); +  fts->account_debit_url = auth->wire_gateway_url; +  fts->exchange_base_url = exchange_base_url; +  fts->payto_debit_account = GNUNET_strdup (payto_debit_account); +  fts->payto_credit_account = payto_credit_account; +  fts->auth = *auth; +  fts->wtid = *wtid; +  if (GNUNET_OK != +      TALER_string_to_amount (amount, +                              &fts->amount)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to parse amount `%s' at %s\n", +                amount, +                label); +    GNUNET_assert (0); +  } + +  { +    struct TALER_TESTING_Command cmd = { +      .cls = fts, +      .label = label, +      .run = &transfer_run, +      .cleanup = &transfer_cleanup, +      .traits = &transfer_traits +    }; + +    return cmd; +  } +} + + +/** + * Modify a transfer command to enable retries when the reserve is not yet + * full or we get other transient errors from the bank. + * + * @param cmd a fakebank transfer command + * @return the command with retries enabled + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_transfer_retry (struct TALER_TESTING_Command cmd) +{ +  struct TransferState *fts; + +  GNUNET_assert (&transfer_run == cmd.run); +  fts = cmd.cls; +  fts->do_retry = GNUNET_YES; +  return cmd; +} + + +/* end of testing_api_cmd_transfer.c */ diff --git a/src/testing/testing_api_cmd_batch.c b/src/testing/testing_api_cmd_batch.c new file mode 100644 index 00000000..fe7c19db --- /dev/null +++ b/src/testing/testing_api_cmd_batch.c @@ -0,0 +1,227 @@ +/* +  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 testing/testing_api_cmd_batch.c + * @brief Implement batch-execution of CMDs. + * @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 "batch" CMD. + */ +struct BatchState +{ +  /** +   * CMDs batch. +   */ +  struct TALER_TESTING_Command *batch; + +  /** +   * Internal command pointer. +   */ +  unsigned int batch_ip; +}; + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command being executed. + * @param is the interpreter state. + */ +static void +batch_run (void *cls, +           const struct TALER_TESTING_Command *cmd, +           struct TALER_TESTING_Interpreter *is) +{ +  struct BatchState *bs = cls; + +  if (NULL != bs->batch[bs->batch_ip].label) +    TALER_LOG_INFO ("Running batched command: %s\n", +                    bs->batch[bs->batch_ip].label); + +  /* hit end command, leap to next top-level command.  */ +  if (NULL == bs->batch[bs->batch_ip].label) +  { +    TALER_LOG_INFO ("Exiting from batch: %s\n", +                    cmd->label); +    TALER_TESTING_interpreter_next (is); +    return; +  } + +  bs->batch[bs->batch_ip].run (bs->batch[bs->batch_ip].cls, +                               &bs->batch[bs->batch_ip], +                               is); +} + + +/** + * 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 +batch_cleanup (void *cls, +               const struct TALER_TESTING_Command *cmd) +{ +  struct BatchState *bs = cls; + +  for (unsigned int i = 0; +       NULL != bs->batch[i].label; +       i++) +    bs->batch[i].cleanup (bs->batch[i].cls, +                          &bs->batch[i]); +  GNUNET_free_non_null (bs->batch); +  GNUNET_free (bs); +} + + +/** + * Offer internal data from a "batch" 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 int +batch_traits (void *cls, +              const void **ret, +              const char *trait, +              unsigned int index) +{ +#define CURRENT_CMD_INDEX 0 +#define BATCH_INDEX 1 + +  struct BatchState *bs = cls; + +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_make_trait_cmd +      (CURRENT_CMD_INDEX, &bs->batch[bs->batch_ip]), +    TALER_TESTING_make_trait_cmd +      (BATCH_INDEX, bs->batch), +    TALER_TESTING_trait_end () +  }; + +  /* Always return current command.  */ +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Create a "batch" command.  Such command takes a + * end_CMD-terminated array of CMDs and executed them. + * Once it hits the end CMD, it passes the control + * to the next top-level CMD, regardless of it being + * another batch or ordinary CMD. + * + * @param label the command label. + * @param batch array of CMDs to execute. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_batch (const char *label, +                         struct TALER_TESTING_Command *batch) +{ +  struct BatchState *bs; +  unsigned int i; + +  bs = GNUNET_new (struct BatchState); + +  /* Get number of commands.  */ +  for (i = 0; NULL != batch[i].label; i++) +    /* noop */ +    ; + +  bs->batch = GNUNET_new_array (i + 1, +                                struct TALER_TESTING_Command); +  memcpy (bs->batch, +          batch, +          sizeof (struct TALER_TESTING_Command) * i); +  { +    struct TALER_TESTING_Command cmd = { +      .cls = bs, +      .label = label, +      .run = &batch_run, +      .cleanup = &batch_cleanup, +      .traits = &batch_traits +    }; + +    return cmd; +  } +} + + +/** + * Advance internal pointer to next command. + * + * @param is interpreter state. + */ +void +TALER_TESTING_cmd_batch_next (struct TALER_TESTING_Interpreter *is) +{ +  struct BatchState *bs = is->commands[is->ip].cls; + +  if (NULL == bs->batch[bs->batch_ip].label) +  { +    is->ip++; +    return; +  } + +  bs->batch_ip++; +} + + +/** + * Test if this command is a batch command. + * + * @return false if not, true if it is a batch command + */ +int +TALER_TESTING_cmd_is_batch (const struct TALER_TESTING_Command *cmd) +{ +  return cmd->run == &batch_run; +} + + +/** + * Obtain what command the batch is at. + * + * @return cmd current batch command + */ +struct TALER_TESTING_Command * +TALER_TESTING_cmd_batch_get_current (const struct TALER_TESTING_Command *cmd) +{ +  struct BatchState *bs = cmd->cls; + +  return &bs->batch[bs->batch_ip]; +} diff --git a/src/testing/testing_api_cmd_check_keys.c b/src/testing/testing_api_cmd_check_keys.c new file mode 100644 index 00000000..6e5c694c --- /dev/null +++ b/src/testing/testing_api_cmd_check_keys.c @@ -0,0 +1,358 @@ +/* +  This file is part of TALER +  (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as +  published by the Free Software Foundation; either version 3, or +  (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_cmd_check_keys.c + * @brief Implementation of "check keys" test command.  XXX-NOTE: + *        the number of 'expected keys' is NOT the number of the + *        downloaded keys, but rather the number of keys that the + *        libtalerutil library keeps locally.  As for the current + *        design, keys are _never_ discarded by the library, + *        therefore their (expected) number is monotonically + *        ascending. + * + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_testing_lib.h" + + +/** + * State for a "check keys" CMD. + */ +struct CheckKeysState +{ +  /** +   * This number will instruct the CMD interpreter to +   * make sure that /keys was downloaded `generation` times +   * _before_ running the very CMD logic. +   */ +  unsigned int generation; + +  /** +   * How many denomination keys the exchange is +   * supposed to have. +   */ +  unsigned int num_denom_keys; + +  /** +   * If this value is GNUNET_YES, then the "cherry +   * picking" facility is turned off; whole /keys is +   * downloaded. +   */ +  unsigned int pull_all_keys; + +  /** +   * If GNUNET_YES, then the user must specify the +   * last_denom_issue_date manually.  This way, it is possible +   * to force whatever X value here (including 0): /keys?last_denom_issue=X. +   */ +  unsigned int set_last_denom; + +  /** +   * Value X to set as the URL parameter: +   * "/keys?last_denom_issue=X" is used only when `set_last_denom' +   * equals GNUNET_YES. +   */ +  struct GNUNET_TIME_Absolute last_denom_date; + +  /** +   * If GNUNET_YES, then we'll provide the "/keys" request. +   * with the "now" argument. +   */ +  int with_now; + +  /** +   * Fake now as passed by the user. +   */ +  struct GNUNET_TIME_Absolute now; + +}; + + +/** + * Run the "check keys" command. + * + * @param cls closure. + * @param cmd the command currently being executed. + * @param is the interpreter state. + */ +static void +check_keys_run (void *cls, +                const struct TALER_TESTING_Command *cmd, +                struct TALER_TESTING_Interpreter *is) +{ +  struct CheckKeysState *cks = cls; + +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "cmd `%s' (ip: %u), key generation: %d\n", +              cmd->label, +              is->ip, +              is->key_generation); + +  if (is->key_generation < cks->generation) +  { +    is->working = GNUNET_NO; +    GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                "Triggering GET /keys, cmd `%s'\n", +                cmd->label); + +    if (GNUNET_YES == cks->set_last_denom) +    { +      TALER_LOG_DEBUG ("Forcing last_denom_date URL argument\n"); +      TALER_EXCHANGE_set_last_denom (is->exchange, +                                     cks->last_denom_date); +    } + +    if (GNUNET_YES == cks->with_now) +      TALER_EXCHANGE_set_now (is->exchange, +                              cks->now); +    /* Redownload /keys.  */ +    GNUNET_break +      (0 == TALER_EXCHANGE_check_keys_current +        (is->exchange, +        GNUNET_YES, +        cks->pull_all_keys).abs_value_us); +    return; +  } + +#if 0 +  /** +   * Not sure this check makes sense: GET /keys is performed on +   * a "maybe" basis, so it can get quite hard to track /keys +   * request.  Rather, this CMD should just check if /keys was +   * requested AT LEAST n times before going ahead with checks. +   */// +  if (is->key_generation > cks->generation) +  { +    /* We got /keys too often, strange. Fatal. May theoretically +       happen if somehow we were really unlucky and /keys expired +       "naturally", but obviously with a sane configuration this +       should also not be. */ +    GNUNET_break (0); +    TALER_LOG_ERROR ("Acutal- vs expected key" +                     " generation: %u vs %u\n", +                     is->key_generation, +                     cks->generation); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +#endif +  /* "/keys" was updated, let's check they were OK! */ +  if (cks->num_denom_keys != is->keys->num_denom_keys) +  { +    /* Did not get the expected number of denomination keys! */ +    GNUNET_break (0); +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Got %u keys in step %s, expected %u\n", +                is->keys->num_denom_keys, +                cmd->label, +                cks->num_denom_keys); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  /* Let's unset the fake now before moving on.  */ +  TALER_EXCHANGE_unset_now (is->exchange); +  TALER_TESTING_interpreter_next (is); +} + + +/** + * Cleanup the state. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +check_keys_cleanup (void *cls, +                    const struct TALER_TESTING_Command *cmd) +{ +  struct CheckKeysState *cks = cls; + +  GNUNET_free (cks); +} + + +/** + * Make a "check keys" command.  This type of command + * checks whether the number of denomination keys from + * @a exchange matches @a num_denom_keys.  Additionally, + * it lets the user set a last denom issue date to be + * used in the request for /keys. + * + * @param label command label + * @param generation when this command is run, exactly @a + *        generation /keys downloads took place.  If the number + *        of downloads is less than @a generation, the logic will + *        first make sure that @a generation downloads are done, + *        and _then_ execute the rest of the command. + * @param num_denom_keys expected number of denomination keys. + * @param last_denom_date date to be set in the "last_denom_issue" + *        URL parameter of /keys. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_check_keys_with_last_denom (const char *label, +                                              unsigned int generation, +                                              unsigned int num_denom_keys, +                                              struct GNUNET_TIME_Absolute +                                              last_denom_date) +{ +  struct CheckKeysState *cks; + +  cks = GNUNET_new (struct CheckKeysState); +  cks->generation = generation; +  cks->num_denom_keys = num_denom_keys; +  cks->set_last_denom = GNUNET_YES; +  cks->last_denom_date = last_denom_date; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = cks, +      .label = label, +      .run = &check_keys_run, +      .cleanup = &check_keys_cleanup +    }; + +    return cmd; +  } +} + + +/** + * Make a "check keys" command.  This type of command + * checks whether the number of denomination keys from + * @a exchange matches @a num_denom_keys. + * + * @param label command label + * @param generation when this command is run, exactly @a + *        generation /keys downloads took place.  If the number + *        of downloads is less than @a generation, the logic will + *        first make sure that @a generation downloads are done, + *        and _then_ execute the rest of the command. + * @param num_denom_keys expected number of denomination keys. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_check_keys (const char *label, +                              unsigned int generation, +                              unsigned int num_denom_keys) +{ +  struct CheckKeysState *cks; + +  cks = GNUNET_new (struct CheckKeysState); +  cks->generation = generation; +  cks->num_denom_keys = num_denom_keys; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = cks, +      .label = label, +      .run = &check_keys_run, +      .cleanup = &check_keys_cleanup +    }; + +    return cmd; +  } +} + + +/** + * Make a "check keys" command.  This type of command + * checks whether the number of denomination keys from + * @a exchange matches @a num_denom_keys. + * + * @param label command label + * @param generation when this command is run, exactly @a + *        generation /keys downloads took place.  If the number + *        of downloads is less than @a generation, the logic will + *        first make sure that @a generation downloads are done, + *        and _then_ execute the rest of the command. + * @param num_denom_keys expected number of denomination keys. + * @param now timestamp to use when fetching keys + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_check_keys_with_now (const char *label, +                                       unsigned int generation, +                                       unsigned int num_denom_keys, +                                       struct GNUNET_TIME_Absolute now) +{ +  struct CheckKeysState *cks; + +  cks = GNUNET_new (struct CheckKeysState); +  cks->generation = generation; +  cks->num_denom_keys = num_denom_keys; +  cks->now = now; +  cks->with_now = GNUNET_YES; + +  /* Force to NOT cherry pick, otherwise they conflict.  */ +  cks->pull_all_keys = GNUNET_YES; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = cks, +      .label = label, +      .run = &check_keys_run, +      .cleanup = &check_keys_cleanup +    }; + +    return cmd; +  } +} + + +/** + * Make a "check keys" command that forcedly does NOT cherry pick; + * just redownload the whole /keys.  Then checks whether the number + * of denomination keys from @a exchange matches @a num_denom_keys. + * + * @param label command label + * @param generation when this command is run, exactly @a + *        generation /keys downloads took place.  If the number + *        of downloads is less than @a generation, the logic will + *        first make sure that @a generation downloads are done, + *        and _then_ execute the rest of the command. + * @param num_denom_keys expected number of denomination keys. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_check_keys_pull_all_keys (const char *label, +                                            unsigned int generation, +                                            unsigned int num_denom_keys) +{ +  struct CheckKeysState *cks; + +  cks = GNUNET_new (struct CheckKeysState); +  cks->generation = generation; +  cks->num_denom_keys = num_denom_keys; +  cks->pull_all_keys = GNUNET_YES; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = cks, +      .label = label, +      .run = &check_keys_run, +      .cleanup = &check_keys_cleanup +    }; + +    return cmd; +  } +} + + +/* end of testing_api_cmd_check_keys.c */ diff --git a/src/testing/testing_api_cmd_deposit.c b/src/testing/testing_api_cmd_deposit.c new file mode 100644 index 00000000..573c68b9 --- /dev/null +++ b/src/testing/testing_api_cmd_deposit.c @@ -0,0 +1,567 @@ +/* +  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_cmd_deposit.c + * @brief command for testing /deposit. + * @author Marcello Stanisci + */ +#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 "deposit" CMD. + */ +struct DepositState +{ + +  /** +   * Amount to deposit. +   */ +  struct TALER_Amount amount; + +  /** +   * Reference to any command that is able to provide a coin. +   */ +  const char *coin_reference; + +  /** +   * If @e coin_reference refers to an operation that generated +   * an array of coins, this value determines which coin to pick. +   */ +  unsigned int coin_index; + +  /** +   * 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; + +  /** +   * Refund deadline. Zero for no refunds. +   */ +  struct GNUNET_TIME_Absolute refund_deadline; + +  /** +   * 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_DepositHandle *dh; + +  /** +   * Timestamp of the /deposit operation. +   */ +  struct GNUNET_TIME_Absolute timestamp; + +  /** +   * 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; + +  /** +   * Should we retry on (transient) failures? +   */ +  int do_retry; + +  /** +   * Set to #GNUNET_YES if the /deposit succeeded +   * and we now can provide the resulting traits. +   */ +  int traits_ready; + +  /** +   * 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; +}; + + +/** + * 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); + + +/** + * Task scheduled to re-try #deposit_run. + * + * @param cls a `struct DepositState` + */ +static void +do_retry (void *cls) +{ +  struct DepositState *ds = cls; + +  ds->retry_task = NULL; +  deposit_run (ds, +               NULL, +               ds->is); +} + + +/** + * Callback to analyze the /deposit response, just used to + * check if the response code is acceptable. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param exchange_sig signature provided by the exchange + *        (NULL on errors) + * @param exchange_pub public key of the exchange, + *        used for signing the response. + * @param obj raw response from the exchange. + */ +static void +deposit_cb (void *cls, +            unsigned int http_status, +            enum TALER_ErrorCode ec, +            const struct TALER_ExchangeSignatureP *exchange_sig, +            const struct TALER_ExchangePublicKeyP *exchange_pub, +            const json_t *obj) +{ +  struct DepositState *ds = cls; + +  ds->dh = NULL; +  if (ds->expected_response_code != http_status) +  { +    if (GNUNET_YES == ds->do_retry) +    { +      if ( (0 == http_status) || +           (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || +           (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                    "Retrying deposit failed with %u/%d\n", +                    http_status, +                    (int) ec); +        /* on DB conflicts, do not use backoff */ +        if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) +          ds->backoff = GNUNET_TIME_UNIT_ZERO; +        else +          ds->backoff = EXCHANGE_LIB_BACKOFF (ds->backoff); +        ds->retry_task +          = GNUNET_SCHEDULER_add_delayed (ds->backoff, +                                          &do_retry, +                                          ds); +        return; +      } +    } +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s in %s:%u\n", +                http_status, +                ds->is->commands[ds->is->ip].label, +                __FILE__, +                __LINE__); +    json_dumpf (obj, stderr, 0); +    TALER_TESTING_interpreter_fail (ds->is); +    return; +  } +  if (MHD_HTTP_OK == http_status) +  { +    ds->traits_ready = GNUNET_YES; +    ds->exchange_pub = *exchange_pub; +    ds->exchange_sig = *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 DepositState *ds = 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_DenominationSignature *denom_pub_sig; +  struct TALER_CoinSpendSignatureP coin_sig; +  struct GNUNET_TIME_Absolute wire_deadline; +  struct GNUNET_CRYPTO_EddsaPrivateKey *merchant_priv; +  struct TALER_MerchantPublicKeyP merchant_pub; +  struct GNUNET_HashCode h_contract_terms; + +  ds->is = is; +  GNUNET_assert (ds->coin_reference); +  coin_cmd = TALER_TESTING_interpreter_lookup_command +               (is, +               ds->coin_reference); +  if (NULL == coin_cmd) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  if ( (GNUNET_OK != +        TALER_TESTING_get_trait_coin_priv (coin_cmd, +                                           ds->coin_index, +                                           &coin_priv)) || +       (GNUNET_OK != +        TALER_TESTING_get_trait_denom_pub (coin_cmd, +                                           ds->coin_index, +                                           &denom_pub)) || +       (GNUNET_OK != +        TALER_TESTING_get_trait_denom_sig (coin_cmd, +                                           ds->coin_index, +                                           &denom_pub_sig)) || +       (GNUNET_OK != +        TALER_JSON_hash (ds->contract_terms, +                         &h_contract_terms)) ) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, +                                      &coin_pub.eddsa_pub); + +  merchant_priv = GNUNET_CRYPTO_eddsa_key_create (); +  ds->merchant_priv.eddsa_priv = *merchant_priv; +  GNUNET_free (merchant_priv); + +  if (0 != ds->refund_deadline.abs_value_us) +  { +    struct GNUNET_TIME_Relative refund_deadline; + +    refund_deadline = GNUNET_TIME_absolute_get_remaining (ds->refund_deadline); +    wire_deadline = GNUNET_TIME_relative_to_absolute +                      (GNUNET_TIME_relative_multiply (refund_deadline, 2)); +  } +  else +  { +    ds->refund_deadline = ds->timestamp; +    wire_deadline = GNUNET_TIME_relative_to_absolute +                      (GNUNET_TIME_UNIT_ZERO); +  } +  GNUNET_CRYPTO_eddsa_key_get_public (&ds->merchant_priv.eddsa_priv, +                                      &merchant_pub.eddsa_pub); + +  (void) GNUNET_TIME_round_abs (&wire_deadline); + +  { +    struct TALER_DepositRequestPS dr; + +    memset (&dr, 0, sizeof (dr)); +    dr.purpose.size = htonl +                        (sizeof (struct TALER_DepositRequestPS)); +    dr.purpose.purpose = htonl +                           (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); +    dr.h_contract_terms = h_contract_terms; +    GNUNET_assert (GNUNET_OK == +                   TALER_JSON_merchant_wire_signature_hash (ds->wire_details, +                                                            &dr.h_wire)); +    dr.timestamp = GNUNET_TIME_absolute_hton (ds->timestamp); +    dr.refund_deadline = GNUNET_TIME_absolute_hton +                           (ds->refund_deadline); +    TALER_amount_hton (&dr.amount_with_fee, +                       &ds->amount); +    TALER_amount_hton (&dr.deposit_fee, +                       &denom_pub->fee_deposit); +    dr.merchant = merchant_pub; +    dr.coin_pub = coin_pub; +    GNUNET_assert (GNUNET_OK == +                   GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv, +                                             &dr.purpose, +                                             &coin_sig.eddsa_signature)); +  } +  ds->dh = TALER_EXCHANGE_deposit (is->exchange, +                                   &ds->amount, +                                   wire_deadline, +                                   ds->wire_details, +                                   &h_contract_terms, +                                   &coin_pub, +                                   denom_pub_sig, +                                   &denom_pub->key, +                                   ds->timestamp, +                                   &merchant_pub, +                                   ds->refund_deadline, +                                   &coin_sig, +                                   &deposit_cb, +                                   ds); + +  if (NULL == ds->dh) +  { +    GNUNET_break (0); +    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 DepositState`. + * @param cmd the command which is being cleaned up. + */ +static void +deposit_cleanup (void *cls, +                 const struct TALER_TESTING_Command *cmd) +{ +  struct DepositState *ds = 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); +    ds->dh = NULL; +  } +  if (NULL != ds->retry_task) +  { +    GNUNET_SCHEDULER_cancel (ds->retry_task); +    ds->retry_task = NULL; +  } +  json_decref (ds->wire_details); +  json_decref (ds->contract_terms); +  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 int +deposit_traits (void *cls, +                const void **ret, +                const char *trait, +                unsigned int index) +{ +  struct DepositState *ds = cls; +  const struct TALER_TESTING_Command *coin_cmd; +  /* Will point to coin cmd internals. */ +  const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv; + +  coin_cmd +    = TALER_TESTING_interpreter_lookup_command (ds->is, +                                                ds->coin_reference); +  if (NULL == coin_cmd) +  { +    GNUNET_break (0); +    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)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (ds->is); +    return GNUNET_NO; +  } +  { +    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_coin_priv (0, +                                          coin_spent_priv), +      TALER_TESTING_make_trait_wire_details (0, +                                             ds->wire_details), +      TALER_TESTING_make_trait_contract_terms (0, +                                               ds->contract_terms), +      TALER_TESTING_make_trait_merchant_priv (0, +                                              &ds->merchant_priv), +      TALER_TESTING_make_trait_amount_obj (0, +                                           &ds->amount), +      TALER_TESTING_trait_end () +    }; + +    return TALER_TESTING_get_trait ((ds->traits_ready) +                                    ? traits +                                    : &traits[2], +                                    ret, +                                    trait, +                                    index); +  } +} + + +/** + * Create a "deposit" command. + * + * @param label command label. + * @param coin_reference reference to any operation that can + *        provide a coin. + * @param coin_index if @a withdraw_reference offers an array of + *        coins, this parameter selects which one in that array. + *        This value is currently ignored, as only one-coin + *        withdrawals are implemented. + * @param target_account_payto target account for the "deposit" + *        request. + * @param contract_terms contract terms to be signed over by the + *        coin. + * @param refund_deadline refund deadline, zero means 'no refunds'. + *        Note, if time were absolute, then it would have come + *        one day and disrupt tests meaning. + * @param amount how much is going to be deposited. + * @param expected_response_code expected HTTP response code. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_deposit (const char *label, +                           const char *coin_reference, +                           unsigned int coin_index, +                           const char *target_account_payto, +                           const char *contract_terms, +                           struct GNUNET_TIME_Relative refund_deadline, +                           const char *amount, +                           unsigned int expected_response_code) +{ +  struct DepositState *ds; +  json_t *wire_details; + +  wire_details = TALER_TESTING_make_wire_details (target_account_payto); +  ds = GNUNET_new (struct DepositState); +  ds->coin_reference = coin_reference; +  ds->coin_index = coin_index; +  ds->wire_details = wire_details; +  ds->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); +  } +  ds->timestamp = GNUNET_TIME_absolute_get (); +  (void) GNUNET_TIME_round_abs (&ds->timestamp); + +  json_object_set_new (ds->contract_terms, +                       "timestamp", +                       GNUNET_JSON_from_time_abs (ds->timestamp)); +  if (0 != refund_deadline.rel_value_us) +  { +    ds->refund_deadline = GNUNET_TIME_relative_to_absolute (refund_deadline); +    (void) GNUNET_TIME_round_abs (&ds->refund_deadline); +    json_object_set_new (ds->contract_terms, +                         "refund_deadline", +                         GNUNET_JSON_from_time_abs (ds->refund_deadline)); +  } +  GNUNET_assert (GNUNET_OK == +                 TALER_string_to_amount (amount, +                                         &ds->amount)); +  ds->expected_response_code = expected_response_code; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ds, +      .label = label, +      .run = &deposit_run, +      .cleanup = &deposit_cleanup, +      .traits = &deposit_traits +    }; + +    return cmd; +  } +} + + +/** + * Modify a deposit command to enable retries when we get transient + * errors from the exchange. + * + * @param cmd a deposit command + * @return the command with retries enabled + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_deposit_with_retry (struct TALER_TESTING_Command cmd) +{ +  struct DepositState *ds; + +  GNUNET_assert (&deposit_run == cmd.run); +  ds = cmd.cls; +  ds->do_retry = GNUNET_YES; +  return cmd; +} + + +/* end of testing_api_cmd_deposit.c */ diff --git a/src/testing/testing_api_cmd_exec_aggregator.c b/src/testing/testing_api_cmd_exec_aggregator.c new file mode 100644 index 00000000..7602cf1d --- /dev/null +++ b/src/testing/testing_api_cmd_exec_aggregator.c @@ -0,0 +1,166 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, +  see <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_cmd_exec_aggregator.c + * @brief run the taler-exchange-aggregator command + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "aggregator" CMD. + */ +struct AggregatorState +{ + +  /** +   * Aggregator process. +   */ +  struct GNUNET_OS_Process *aggregator_proc; + +  /** +   * Configuration file used by the aggregator. +   */ +  const char *config_filename; +}; + + +/** + * Run the command.  Use the `taler-exchange-aggregator' program. + * + * @param cls closure. + * @param cmd command being run. + * @param is interpreter state. + */ +static void +aggregator_run (void *cls, +                const struct TALER_TESTING_Command *cmd, +                struct TALER_TESTING_Interpreter *is) +{ +  struct AggregatorState *as = cls; + +  as->aggregator_proc +    = GNUNET_OS_start_process (GNUNET_NO, +                               GNUNET_OS_INHERIT_STD_ALL, +                               NULL, NULL, NULL, +                               "taler-exchange-aggregator", +                               "taler-exchange-aggregator", +                               "-c", as->config_filename, +                               "-t", /* exit when done */ +                               NULL); +  if (NULL == as->aggregator_proc) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "aggregator" CMD, and possibly kill its + * process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +aggregator_cleanup (void *cls, +                    const struct TALER_TESTING_Command *cmd) +{ +  struct AggregatorState *as = cls; + +  if (NULL != as->aggregator_proc) +  { +    GNUNET_break (0 == +                  GNUNET_OS_process_kill (as->aggregator_proc, +                                          SIGKILL)); +    GNUNET_OS_process_wait (as->aggregator_proc); +    GNUNET_OS_process_destroy (as->aggregator_proc); +    as->aggregator_proc = NULL; +  } +  GNUNET_free (as); +} + + +/** + * Offer "aggregator" CMD internal data to other commands. + * + * @param cls closure. + * @param[out] ret result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success + */ +static int +aggregator_traits (void *cls, +                   const void **ret, +                   const char *trait, +                   unsigned int index) +{ +  struct AggregatorState *as = cls; +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_make_trait_process (0, &as->aggregator_proc), +    TALER_TESTING_trait_end () +  }; + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Make a "aggregator" CMD. + * + * @param label command label. + * @param config_filename configuration file for the + *                        aggregator to use. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_aggregator (const char *label, +                                   const char *config_filename) +{ +  struct AggregatorState *as; + +  as = GNUNET_new (struct AggregatorState); +  as->config_filename = config_filename; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = as, +      .label = label, +      .run = &aggregator_run, +      .cleanup = &aggregator_cleanup, +      .traits = &aggregator_traits +    }; + +    return cmd; +  } +} + + +/* end of testing_api_cmd_exec_aggregator.c */ diff --git a/src/testing/testing_api_cmd_exec_auditor-sign.c b/src/testing/testing_api_cmd_exec_auditor-sign.c new file mode 100644 index 00000000..fec7da7c --- /dev/null +++ b/src/testing/testing_api_cmd_exec_auditor-sign.c @@ -0,0 +1,232 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, +  see <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/testing_api_cmd_exec_auditor-sign.c + * @brief run the taler-exchange-aggregator command + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "auditor sign" CMD. + */ +struct AuditorSignState +{ + +  /** +   * Handle to the process making the signature. +   */ +  struct GNUNET_OS_Process *auditor_sign_proc; + +  /** +   * Configuration file used by the command. +   */ +  const char *config_filename; + +  /** +   * File name of signed blob. +   */ +  char *signed_keys_out; +}; + + +/** + * Run the command; calls the `taler-auditor-sign' program. + * + * @param cls closure. + * @param cmd the command. + * @param is interpreter state. + */ +static void +auditor_sign_run (void *cls, +                  const struct TALER_TESTING_Command *cmd, +                  struct TALER_TESTING_Interpreter *is) +{ +  struct AuditorSignState *ass = cls; + +  struct GNUNET_CONFIGURATION_Handle *cfg; +  char *test_home_dir; +  char *exchange_master_pub; +  struct GNUNET_TIME_Absolute now; + +  cfg = GNUNET_CONFIGURATION_create (); +  if (GNUNET_OK != GNUNET_CONFIGURATION_load +        (cfg, ass->config_filename)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_filename (cfg, +                                               "paths", +                                               "TALER_TEST_HOME", +                                               &test_home_dir)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                               "paths", +                               "TALER_TEST_HOME"); +    GNUNET_CONFIGURATION_destroy (cfg); +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  now = GNUNET_TIME_absolute_get (); +  GNUNET_asprintf +    (&ass->signed_keys_out, +    "%s/.local/share/taler/auditors/auditor-%llu.out", +    test_home_dir, +    (unsigned long long) now.abs_value_us); +  GNUNET_free (test_home_dir); + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_string (cfg, +                                             "exchange", +                                             "MASTER_PUBLIC_KEY", +                                             &exchange_master_pub)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                               "exchange", +                               "MASTER_PUBLIC_KEY"); +    GNUNET_CONFIGURATION_destroy (cfg); + +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  GNUNET_CONFIGURATION_destroy (cfg); + +  ass->auditor_sign_proc = GNUNET_OS_start_process +                             (GNUNET_NO, +                             GNUNET_OS_INHERIT_STD_ALL, +                             NULL, NULL, NULL, +                             "taler-auditor-sign", +                             "taler-auditor-sign", +                             "-c", ass->config_filename, +                             "-u", "http://auditor/", +                             "-m", exchange_master_pub, +                             "-r", "auditor.in", +                             "-o", ass->signed_keys_out, +                             NULL); +  GNUNET_free (exchange_master_pub); +  if (NULL == ass->auditor_sign_proc) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "auditor sign" CMD, and possibly + * kill its process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +auditor_sign_cleanup (void *cls, +                      const struct TALER_TESTING_Command *cmd) +{ +  struct AuditorSignState *ass = cls; + +  if (NULL != ass->auditor_sign_proc) +  { +    GNUNET_break (0 == GNUNET_OS_process_kill +                    (ass->auditor_sign_proc, SIGKILL)); +    GNUNET_OS_process_wait (ass->auditor_sign_proc); +    GNUNET_OS_process_destroy (ass->auditor_sign_proc); +    ass->auditor_sign_proc = NULL; +  } +  GNUNET_free_non_null (ass->signed_keys_out); +  GNUNET_free (ass); +} + + +/** + * Offer "auditor sign" CMD internal data to other commands. + * + * @param cls closure. + * @param[out] ret result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +auditor_sign_traits (void *cls, +                     const void **ret, +                     const char *trait, +                     unsigned int index) +{ +  struct AuditorSignState *ass = cls; +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_make_trait_process (0, &ass->auditor_sign_proc), +    TALER_TESTING_trait_end () +  }; + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Make a "auditor sign" CMD. + * + * @param label command label + * @param config_filename configuration filename + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_auditor_sign (const char *label, +                                     const char *config_filename) +{ +  struct AuditorSignState *ass; + +  ass = GNUNET_new (struct AuditorSignState); +  ass->config_filename = config_filename; + +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ass, +      .label = label, +      .run = &auditor_sign_run, +      .cleanup = &auditor_sign_cleanup, +      .traits = &auditor_sign_traits +    }; + +    return cmd; +  } +} + + +/* end of testing_api_cmd_exec_auditor-sign.c */ diff --git a/src/testing/testing_api_cmd_exec_keyup.c b/src/testing/testing_api_cmd_exec_keyup.c new file mode 100644 index 00000000..cc4fb9e2 --- /dev/null +++ b/src/testing/testing_api_cmd_exec_keyup.c @@ -0,0 +1,235 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, +  see <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/testing_api_cmd_exec_keyup.c + * @brief run the taler-exchange-keyup command + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "keyup" CMD. + */ +struct KeyupState +{ + +  /** +   * Process for the "keyup" command. +   */ +  struct GNUNET_OS_Process *keyup_proc; + +  /** +   * Configuration file used by the command. +   */ +  const char *config_filename; + +  /** +   * If GNUNET_YES, then the fake @e now value will be +   * passed to taler-exchange-keyup via the --time +   * option. +   */ +  unsigned int with_now; + +  /** +   * User-provided fake now. +   */ +  struct GNUNET_TIME_Absolute now; +}; + + +/** + * Run the command; calls the `taler-exchange-keyup' program. + * + * @param cls closure. + * @param cmd the commaind being run. + * @param is interpreter state. + */ +static void +keyup_run (void *cls, +           const struct TALER_TESTING_Command *cmd, +           struct TALER_TESTING_Interpreter *is) +{ +  struct KeyupState *ks = cls; + +  if (GNUNET_YES == ks->with_now) +  { +    ks->keyup_proc = GNUNET_OS_start_process +                       (GNUNET_NO, +                       GNUNET_OS_INHERIT_STD_ALL, +                       NULL, NULL, NULL, +                       "taler-exchange-keyup", +                       "taler-exchange-keyup", +                       "-c", ks->config_filename, +                       "-o", "auditor.in", +                       "--time", +                       GNUNET_STRINGS_absolute_time_to_string (ks->now), +                       NULL); +  } +  else +    ks->keyup_proc = GNUNET_OS_start_process +                       (GNUNET_NO, +                       GNUNET_OS_INHERIT_STD_ALL, +                       NULL, NULL, NULL, +                       "taler-exchange-keyup", +                       "taler-exchange-keyup", +                       "-c", ks->config_filename, +                       "-o", "auditor.in", +                       NULL); + +  if (NULL == ks->keyup_proc) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  /* This function does not tell whether the command +   * succeeded or not!  */ +  TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "keyup" CMD, and possibly kills its + * process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +keyup_cleanup (void *cls, +               const struct TALER_TESTING_Command *cmd) +{ +  struct KeyupState *ks = cls; + +  if (NULL != ks->keyup_proc) +  { +    GNUNET_break (0 == +                  GNUNET_OS_process_kill (ks->keyup_proc, +                                          SIGKILL)); +    GNUNET_OS_process_wait (ks->keyup_proc); +    GNUNET_OS_process_destroy (ks->keyup_proc); +    ks->keyup_proc = NULL; +  } +  GNUNET_free (ks); +} + + +/** + * Offer "keyup" CMD internal data to other commands. + * + * @param cls closure. + * @param[out] ret result + * @param trait name of the trait. + * @param index index number of the object to offer. + * + * @return #GNUNET_OK on success. + */ +static int +keyup_traits (void *cls, +              const void **ret, +              const char *trait, +              unsigned int index) +{ +  struct KeyupState *ks = cls; +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_make_trait_process (0, &ks->keyup_proc), +    TALER_TESTING_trait_end () +  }; + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Make the "keyup" CMD, with "--timestamp" option. + * + * @param label command label. + * @param config_filename configuration filename. + * @param now Unix timestamp representing the fake "now". + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_keyup_with_now +  (const char *label, +  const char *config_filename, +  struct GNUNET_TIME_Absolute now) +{ +  struct KeyupState *ks; + +  ks = GNUNET_new (struct KeyupState); +  ks->config_filename = config_filename; +  ks->now = now; +  ks->with_now = GNUNET_YES; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ks, +      .label = label, +      .run = &keyup_run, +      .cleanup = &keyup_cleanup, +      .traits = &keyup_traits +    }; + +    return cmd; +  } +} + + +/** + * Make the "keyup" CMD. + * + * @param label command label. + * @param config_filename configuration filename. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_keyup (const char *label, +                              const char *config_filename) +{ +  struct KeyupState *ks; + +  ks = GNUNET_new (struct KeyupState); +  ks->config_filename = config_filename; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ks, +      .label = label, +      .run = &keyup_run, +      .cleanup = &keyup_cleanup, +      .traits = &keyup_traits +    }; + +    return cmd; +  } +} + + +/* end of testing_api_cmd_exec_keyup.c */ diff --git a/src/testing/testing_api_cmd_exec_wirewatch.c b/src/testing/testing_api_cmd_exec_wirewatch.c new file mode 100644 index 00000000..44de9683 --- /dev/null +++ b/src/testing/testing_api_cmd_exec_wirewatch.c @@ -0,0 +1,167 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as +  published by the Free Software Foundation; either version 3, +  or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, +  but WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_cmd_exec_wirewatch.c + * @brief run the taler-exchange-wirewatch 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 "wirewatch" CMD. + */ +struct WirewatchState +{ + +  /** +   * Process for the wirewatcher. +   */ +  struct GNUNET_OS_Process *wirewatch_proc; + +  /** +   * Configuration file used by the wirewatcher. +   */ +  const char *config_filename; +}; + +/** + * Run the command; use the `taler-exchange-wirewatch' program. + * + * @param cls closure. + * @param cmd command currently being executed. + * @param is interpreter state. + */ +static void +wirewatch_run (void *cls, +               const struct TALER_TESTING_Command *cmd, +               struct TALER_TESTING_Interpreter *is) +{ +  struct WirewatchState *ws = cls; + +  ws->wirewatch_proc +    = GNUNET_OS_start_process (GNUNET_NO, +                               GNUNET_OS_INHERIT_STD_ALL, +                               NULL, NULL, NULL, +                               "taler-exchange-wirewatch", +                               "taler-exchange-wirewatch", +                               "-c", ws->config_filename, +                               "-T", /* exit when done */ +                               NULL); +  if (NULL == ws->wirewatch_proc) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "wirewatch" CMD, and possibly + * kills its process if it did not terminate regularly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +wirewatch_cleanup (void *cls, +                   const struct TALER_TESTING_Command *cmd) +{ +  struct WirewatchState *ws = cls; + +  if (NULL != ws->wirewatch_proc) +  { +    GNUNET_break (0 == +                  GNUNET_OS_process_kill (ws->wirewatch_proc, +                                          SIGKILL)); +    GNUNET_OS_process_wait (ws->wirewatch_proc); +    GNUNET_OS_process_destroy (ws->wirewatch_proc); +    ws->wirewatch_proc = NULL; +  } +  GNUNET_free (ws); +} + + +/** + * Offer "wirewatch" CMD internal data to other commands. + * + * @param cls closure. + * @param[out] ret result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +wirewatch_traits (void *cls, +                  const void **ret, +                  const char *trait, +                  unsigned int index) +{ +  struct WirewatchState *ws = cls; +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_make_trait_process (0, +                                      &ws->wirewatch_proc), +    TALER_TESTING_trait_end () +  }; + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Make a "wirewatch" CMD. + * + * @param label command label. + * @param config_filename configuration filename. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_wirewatch (const char *label, +                                  const char *config_filename) +{ +  struct WirewatchState *ws; + +  ws = GNUNET_new (struct WirewatchState); +  ws->config_filename = config_filename; + +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ws, +      .label = label, +      .run = &wirewatch_run, +      .cleanup = &wirewatch_cleanup, +      .traits = &wirewatch_traits +    }; + +    return cmd; +  } +} + + +/* end of testing_api_cmd_exec_wirewatch.c */ diff --git a/src/testing/testing_api_cmd_insert_deposit.c b/src/testing/testing_api_cmd_insert_deposit.c new file mode 100644 index 00000000..b66a4bfa --- /dev/null +++ b/src/testing/testing_api_cmd_insert_deposit.c @@ -0,0 +1,318 @@ +/* +  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_insert_deposit.c + * @brief deposit a coin directly into the database. + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" +#include "taler_exchangedb_plugin.h" + + +/** + * State for a "insert-deposit" CMD. + */ +struct InsertDepositState +{ +  /** +   * Configuration file used by the command. +   */ +  const struct TALER_TESTING_DatabaseConnection *dbc; + +  /** +   * Human-readable name of the shop. +   */ +  const char *merchant_name; + +  /** +   * Merchant account name (NOT a payto-URI). +   */ +  const char *merchant_account; + +  /** +   * Deadline before which the aggregator should +   * send the payment to the merchant. +   */ +  struct GNUNET_TIME_Relative wire_deadline; + +  /** +   * Amount to deposit, inclusive of deposit fee. +   */ +  const char *amount_with_fee; + +  /** +   * Deposit fee. +   */ +  const char *deposit_fee; +}; + +/** + * Setup (fake) information about a coin used in deposit. + * + * @param[out] issue information to initialize with "valid" data + */ +static void +fake_issue (struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue) +{ +  memset (issue, 0, sizeof (struct +                            TALER_EXCHANGEDB_DenominationKeyInformationP)); +  GNUNET_assert (GNUNET_OK == +                 TALER_string_to_amount_nbo ("EUR:1", +                                             &issue->properties.value)); +  GNUNET_assert (GNUNET_OK == +                 TALER_string_to_amount_nbo ("EUR:0.1", +                                             &issue->properties.fee_withdraw)); +  GNUNET_assert (GNUNET_OK == +                 TALER_string_to_amount_nbo ("EUR:0.1", +                                             &issue->properties.fee_deposit)); +  GNUNET_assert (GNUNET_OK == +                 TALER_string_to_amount_nbo ("EUR:0.1", +                                             &issue->properties.fee_refresh)); +  GNUNET_assert (GNUNET_OK == +                 TALER_string_to_amount_nbo ("EUR:0.1", +                                             &issue->properties.fee_refund)); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the commaind being run. + * @param is interpreter state. + */ +static void +insert_deposit_run (void *cls, +                    const struct TALER_TESTING_Command *cmd, +                    struct TALER_TESTING_Interpreter *is) +{ +  struct InsertDepositState *ids = cls; +  struct TALER_EXCHANGEDB_Deposit deposit; +  struct TALER_MerchantPrivateKeyP merchant_priv; +  struct TALER_EXCHANGEDB_DenominationKeyInformationP issue; +  struct TALER_DenominationPublicKey dpk; +  struct GNUNET_CRYPTO_RsaPrivateKey *denom_priv; +  struct GNUNET_HashCode hc; + +  // prepare and store issue first. +  fake_issue (&issue); +  denom_priv = GNUNET_CRYPTO_rsa_private_key_create (1024); +  dpk.rsa_public_key = GNUNET_CRYPTO_rsa_private_key_get_public (denom_priv); +  GNUNET_CRYPTO_rsa_public_key_hash (dpk.rsa_public_key, +                                     &issue.properties.denom_hash); + +  if ( (GNUNET_OK != +        ids->dbc->plugin->start (ids->dbc->plugin->cls, +                                 ids->dbc->session, +                                 "talertestinglib: denomination insertion")) || +       (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != +        ids->dbc->plugin->insert_denomination_info (ids->dbc->plugin->cls, +                                                    ids->dbc->session, +                                                    &dpk, +                                                    &issue)) || +       (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != +        ids->dbc->plugin->commit (ids->dbc->plugin->cls, +                                  ids->dbc->session)) ) +  { +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  /* prepare and store deposit now. */ +  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); +  GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv.eddsa_priv, +                                      &deposit.merchant_pub.eddsa_pub); +  GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_WEAK, +                                    &deposit.h_contract_terms); +  if ( (GNUNET_OK != +        TALER_string_to_amount (ids->amount_with_fee, +                                &deposit.amount_with_fee)) || +       (GNUNET_OK != +        TALER_string_to_amount (ids->deposit_fee, +                                &deposit.deposit_fee)) ) +  { +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  GNUNET_CRYPTO_rsa_public_key_hash (dpk.rsa_public_key, +                                     &deposit.coin.denom_pub_hash); + +  GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_WEAK, +                                    &hc); +  deposit.coin.denom_sig.rsa_signature = GNUNET_CRYPTO_rsa_sign_fdh (denom_priv, +                                                                     &hc); +  { +    char *str; + +    GNUNET_asprintf (&str, +                     "payto://x-taler-bank/localhost/%s", +                     ids->merchant_account); +    deposit.receiver_wire_account +      = json_pack ("{s:s, s:s}", +                   "salt", "this-is-a-salt-value", +                   "url", str); +    GNUNET_free (str); +  } + +  GNUNET_assert (GNUNET_OK == +                 TALER_JSON_merchant_wire_signature_hash ( +                   deposit.receiver_wire_account, +                   &deposit.h_wire)); +  deposit.timestamp = GNUNET_TIME_absolute_get (); +  GNUNET_TIME_round_abs (&deposit.timestamp); +  deposit.wire_deadline = GNUNET_TIME_relative_to_absolute ( +    ids->wire_deadline); +  GNUNET_TIME_round_abs (&deposit.wire_deadline); + +  /* finally, actually perform the DB operation */ +  if ( (GNUNET_OK != +        ids->dbc->plugin->start (ids->dbc->plugin->cls, +                                 ids->dbc->session, +                                 "libtalertesting: insert deposit")) || +       (0 > +        ids->dbc->plugin->ensure_coin_known (ids->dbc->plugin->cls, +                                             ids->dbc->session, +                                             &deposit.coin)) || +       (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != +        ids->dbc->plugin->insert_deposit (ids->dbc->plugin->cls, +                                          ids->dbc->session, +                                          &deposit)) || +       (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != +        ids->dbc->plugin->commit (ids->dbc->plugin->cls, +                                  ids->dbc->session)) ) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +  } + +  GNUNET_CRYPTO_rsa_signature_free (deposit.coin.denom_sig.rsa_signature); +  GNUNET_CRYPTO_rsa_public_key_free (dpk.rsa_public_key); +  GNUNET_CRYPTO_rsa_private_key_free (denom_priv); +  json_decref (deposit.receiver_wire_account); + +  TALER_TESTING_interpreter_next (is); +} + + +/** + * Free the state of a "auditor-dbinit" CMD, and possibly kills its + * process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +insert_deposit_cleanup (void *cls, +                        const struct TALER_TESTING_Command *cmd) +{ +  struct InsertDepositState *ids = cls; + +  GNUNET_free (ids); +} + + +/** + * Offer "insert-deposit" CMD internal data to other commands. + * + * @param cls closure. + * @param[out] ret result + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +insert_deposit_traits (void *cls, +                       const void **ret, +                       const char *trait, +                       unsigned int index) +{ +  (void) cls; +  (void) ret; +  (void) trait; +  (void) index; +  return GNUNET_NO; +} + + +/** + * Make the "insert-deposit" CMD. + * + * @param label command label. + * @param dbc collects database plugin and session handles. + * @param merchant_name Human-readable name of the merchant. + * @param merchant_account merchant's account name (NOT a payto:// URI) + * @param wire_deadline point in time where the aggregator should have + *        wired money to the merchant. + * @param amount_with_fee amount to deposit (inclusive of deposit fee) + * @param deposit_fee deposit fee + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_insert_deposit (const char *label, +                                  const struct +                                  TALER_TESTING_DatabaseConnection *dbc, +                                  const char *merchant_name, +                                  const char *merchant_account, +                                  struct GNUNET_TIME_Relative wire_deadline, +                                  const char *amount_with_fee, +                                  const char *deposit_fee) +{ +  struct InsertDepositState *ids; + +  ids = GNUNET_new (struct InsertDepositState); +  ids->dbc = dbc; +  ids->merchant_name = merchant_name; +  ids->merchant_account = merchant_account; +  ids->wire_deadline = wire_deadline; +  ids->amount_with_fee = amount_with_fee; +  ids->deposit_fee = deposit_fee; + +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ids, +      .label = label, +      .run = &insert_deposit_run, +      .cleanup = &insert_deposit_cleanup, +      .traits = &insert_deposit_traits +    }; + +    return cmd; +  } +} + + +/* end of testing_api_cmd_insert_deposit.c */ diff --git a/src/testing/testing_api_cmd_recoup.c b/src/testing/testing_api_cmd_recoup.c new file mode 100644 index 00000000..c12f67f6 --- /dev/null +++ b/src/testing/testing_api_cmd_recoup.c @@ -0,0 +1,619 @@ +/* +  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 testing/testing_api_cmd_recoup.c + * @brief Implement the /revoke and /recoup test commands. + * @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 "revoke" CMD. + */ +struct RevokeState +{ +  /** +   * Expected HTTP status code. +   */ +  unsigned int expected_response_code; + +  /** +   * Command that offers a denomination to revoke. +   */ +  const char *coin_reference; + +  /** +   * The interpreter state. +   */ +  struct TALER_TESTING_Interpreter *is; + +  /** +   * The revoke process handle. +   */ +  struct GNUNET_OS_Process *revoke_proc; + +  /** +   * Configuration file name. +   */ +  const char *config_filename; + +  /** +   * Encoding of the denomination (to revoke) public key hash. +   */ +  char *dhks; + +}; + + +/** + * State for a "pay back" CMD. + */ +struct RecoupState +{ +  /** +   * Expected HTTP status code. +   */ +  unsigned int expected_response_code; + +  /** +   * Command that offers a reserve private key, +   * plus a coin to be paid back. +   */ +  const char *coin_reference; + +  /** +   * The interpreter state. +   */ +  struct TALER_TESTING_Interpreter *is; + +  /** +   * Amount expected to be paid back. +   */ +  const char *amount; + +  /** +   * Handle to the ongoing operation. +   */ +  struct TALER_EXCHANGE_RecoupHandle *ph; + +  /** +   * NULL if coin was not refreshed, otherwise reference +   * to the melt operation underlying @a coin_reference. +   */ +  const char *melt_reference; + +}; + + +/** + * Parser reference to a coin. + * + * @param coin_reference of format $LABEL['#' $INDEX]? + * @param[out] cref where we return a copy of $LABEL + * @param[out] idx where we set $INDEX + * @return #GNUNET_SYSERR if $INDEX is present but not numeric + */ +static int +parse_coin_reference (const char *coin_reference, +                      char **cref, +                      unsigned int *idx) +{ +  const char *index; + +  /* We allow command references of the form "$LABEL#$INDEX" or +     just "$LABEL", which implies the index is 0. Figure out +     which one it is. */ +  index = strchr (coin_reference, '#'); +  if (NULL == index) +  { +    *idx = 0; +    *cref = GNUNET_strdup (coin_reference); +    return GNUNET_OK; +  } +  *cref = GNUNET_strndup (coin_reference, +                          index - coin_reference); +  if (1 != sscanf (index + 1, +                   "%u", +                   idx)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Numeric index (not `%s') required after `#' in command reference of command in %s:%u\n", +                index, +                __FILE__, +                __LINE__); +    GNUNET_free (*cref); +    *cref = NULL; +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * Check the result of the recoup request: checks whether + * the HTTP response code is good, and that the coin that + * was paid back belonged to the right reserve. + * + * @param cls closure + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param amount amount the exchange will wire back for this coin. + * @param timestamp what time did the exchange receive the + *        /recoup request + * @param reserve_pub public key of the reserve receiving the recoup, NULL if refreshed or on error + * @param old_coin_pub public key of the dirty coin, NULL if not refreshed or on error + * @param full_response raw response from the exchange. + */ +static void +recoup_cb (void *cls, +           unsigned int http_status, +           enum TALER_ErrorCode ec, +           const struct TALER_Amount *amount, +           struct GNUNET_TIME_Absolute timestamp, +           const struct TALER_ReservePublicKeyP *reserve_pub, +           const struct TALER_CoinSpendPublicKeyP *old_coin_pub, +           const json_t *full_response) +{ +  struct RecoupState *ps = cls; +  struct TALER_TESTING_Interpreter *is = ps->is; +  struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; +  const struct TALER_TESTING_Command *reserve_cmd; +  char *cref; +  unsigned int idx; + +  ps->ph = NULL; +  if (ps->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s in %s:%u\n", +                http_status, +                cmd->label, +                __FILE__, +                __LINE__); +    json_dumpf (full_response, stderr, 0); +    fprintf (stderr, "\n"); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  if (GNUNET_OK != +      parse_coin_reference (ps->coin_reference, +                            &cref, +                            &idx)) +  { +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  reserve_cmd = TALER_TESTING_interpreter_lookup_command +                  (is, cref); +  GNUNET_free (cref); + +  if (NULL == reserve_cmd) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  switch (http_status) +  { +  case MHD_HTTP_OK: +    /* first, check amount */ +    { +      struct TALER_Amount expected_amount; + +      if (GNUNET_OK != +          TALER_string_to_amount (ps->amount, &expected_amount)) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } +      if (0 != TALER_amount_cmp (amount, &expected_amount)) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Total amount missmatch to command %s\n", +                    cmd->label); +        json_dumpf (full_response, stderr, 0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } +    } +    /* now, check old_coin_pub or reserve_pub, respectively */ +    if (NULL != ps->melt_reference) +    { +      const struct TALER_TESTING_Command *melt_cmd; +      const struct TALER_CoinSpendPrivateKeyP *dirty_priv; +      struct TALER_CoinSpendPublicKeyP oc; + +      melt_cmd = TALER_TESTING_interpreter_lookup_command (is, +                                                           ps->melt_reference); +      if (NULL == melt_cmd) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } +      if (GNUNET_OK != +          TALER_TESTING_get_trait_coin_priv (melt_cmd, +                                             0, +                                             &dirty_priv)) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } +      GNUNET_CRYPTO_eddsa_key_get_public (&dirty_priv->eddsa_priv, +                                          &oc.eddsa_pub); +      if (0 != GNUNET_memcmp (&oc, +                              old_coin_pub)) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } +    } +    else +    { +      const struct TALER_ReservePrivateKeyP *reserve_priv; +      struct TALER_ReservePublicKeyP rp; + +      if (NULL == reserve_pub) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } +      if (GNUNET_OK != TALER_TESTING_get_trait_reserve_priv +            (reserve_cmd, idx, &reserve_priv)) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } +      GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, +                                          &rp.eddsa_pub); +      if (0 != GNUNET_memcmp (reserve_pub, &rp)) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } +    } +    break; +  default: +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Unmanaged HTTP status code %u.\n", +                http_status); +    break; +  } +  TALER_TESTING_interpreter_next (is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +recoup_run (void *cls, +            const struct TALER_TESTING_Command *cmd, +            struct TALER_TESTING_Interpreter *is) +{ +  struct RecoupState *ps = cls; +  const struct TALER_TESTING_Command *coin_cmd; +  const struct TALER_CoinSpendPrivateKeyP *coin_priv; +  const struct TALER_DenominationBlindingKeyP *blinding_key; +  const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; +  const struct TALER_DenominationSignature *coin_sig; +  struct TALER_PlanchetSecretsP planchet; +  char *cref; +  unsigned int idx; + +  ps->is = is; +  if (GNUNET_OK != +      parse_coin_reference (ps->coin_reference, +                            &cref, +                            &idx)) +  { +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  coin_cmd = TALER_TESTING_interpreter_lookup_command +               (is, cref); +  GNUNET_free (cref); + +  if (NULL == coin_cmd) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv +        (coin_cmd, idx, &coin_priv)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  if (GNUNET_OK != TALER_TESTING_get_trait_blinding_key +        (coin_cmd, idx, &blinding_key)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  planchet.coin_priv = *coin_priv; +  planchet.blinding_key = *blinding_key; + +  if (GNUNET_OK != TALER_TESTING_get_trait_denom_pub +        (coin_cmd, idx, &denom_pub)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  if (GNUNET_OK != TALER_TESTING_get_trait_denom_sig +        (coin_cmd, idx, &coin_sig)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "Trying to get '%s..' paid back\n", +              TALER_B2S (&denom_pub->h_key)); + +  ps->ph = TALER_EXCHANGE_recoup (is->exchange, +                                  denom_pub, +                                  coin_sig, +                                  &planchet, +                                  NULL != ps->melt_reference, +                                  recoup_cb, +                                  ps); +  GNUNET_assert (NULL != ps->ph); +} + + +/** + * Cleanup the state. + * + * @param cls closure, must be a `struct RevokeState`. + * @param cmd the command which is being cleaned up. + */ +static void +revoke_cleanup (void *cls, +                const struct TALER_TESTING_Command *cmd) +{ +  struct RevokeState *rs = cls; + +  if (NULL != rs->revoke_proc) +  { +    GNUNET_break (0 == GNUNET_OS_process_kill +                    (rs->revoke_proc, SIGKILL)); +    GNUNET_OS_process_wait (rs->revoke_proc); +    GNUNET_OS_process_destroy (rs->revoke_proc); +    rs->revoke_proc = NULL; +  } +  GNUNET_free_non_null (rs->dhks); +  GNUNET_free (rs); +} + + +/** + * Cleanup the "recoup" CMD state, and possibly cancel + * a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +recoup_cleanup (void *cls, +                const struct TALER_TESTING_Command *cmd) +{ +  struct RecoupState *ps = cls; +  if (NULL != ps->ph) +  { +    TALER_EXCHANGE_recoup_cancel (ps->ph); +    ps->ph = NULL; +  } +  GNUNET_free (ps); +} + + +/** + * Offer internal data from a "revoke" CMD to other CMDs. + * + * @param cls closure + * @param[out] ret result (could be anything) + * @param trait name of the trait + * @param index index number of the object to offer. + * @return #GNUNET_OK on success + */ +static int +revoke_traits (void *cls, +               const void **ret, +               const char *trait, +               unsigned int index) +{ +  struct RevokeState *rs = cls; +  struct TALER_TESTING_Trait traits[] = { +    /* Needed by the handler which waits the proc' +     * death and calls the next command */ +    TALER_TESTING_make_trait_process (0, &rs->revoke_proc), +    TALER_TESTING_trait_end () +  }; + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Run the "revoke" command.  The core of the function + * is to call the "keyup" utility passing it the base32 + * encoding of the denomination to revoke. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +revoke_run (void *cls, +            const struct TALER_TESTING_Command *cmd, +            struct TALER_TESTING_Interpreter *is) +{ +  struct RevokeState *rs = cls; +  const struct TALER_TESTING_Command *coin_cmd; +  const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; + +  rs->is = is; +  /* Get denom pub from trait */ +  coin_cmd = TALER_TESTING_interpreter_lookup_command +               (is, rs->coin_reference); + +  if (NULL == coin_cmd) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  GNUNET_assert (GNUNET_OK == TALER_TESTING_get_trait_denom_pub +                   (coin_cmd, 0, &denom_pub)); + +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "Trying to revoke denom '%s..'\n", +              TALER_B2S (&denom_pub->h_key)); + +  rs->dhks = GNUNET_STRINGS_data_to_string_alloc +               (&denom_pub->h_key, sizeof (struct GNUNET_HashCode)); + +  rs->revoke_proc = GNUNET_OS_start_process +                      (GNUNET_NO, +                      GNUNET_OS_INHERIT_STD_ALL, +                      NULL, NULL, NULL, +                      "taler-exchange-keyup", +                      "taler-exchange-keyup", +                      "-c", rs->config_filename, +                      "-r", rs->dhks, +                      NULL); + +  if (NULL == rs->revoke_proc) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "Revoke is ongoing..\n"); + +  is->reload_keys = GNUNET_OK; +  TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Make a "recoup" command. + * + * @param label the command label + * @param expected_response_code expected HTTP status code + * @param coin_reference reference to any command which + *        offers a coin & reserve private key. + * @param amount denomination to pay back. + * @param melt_reference NULL if coin was not refreshed + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_recoup (const char *label, +                          unsigned int expected_response_code, +                          const char *coin_reference, +                          const char *amount, +                          const char *melt_reference) +{ +  struct RecoupState *ps; + +  ps = GNUNET_new (struct RecoupState); +  ps->expected_response_code = expected_response_code; +  ps->coin_reference = coin_reference; +  ps->amount = amount; +  ps->melt_reference = melt_reference; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ps, +      .label = label, +      .run = &recoup_run, +      .cleanup = &recoup_cleanup +    }; + +    return cmd; +  } +} + + +/** + * Make a "revoke" command. + * + * @param label the command label. + * @param expected_response_code expected HTTP status code. + * @param coin_reference reference to a CMD that will offer the + *        denomination to revoke. + * @param config_filename configuration file name. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_revoke (const char *label, +                          unsigned int expected_response_code, +                          const char *coin_reference, +                          const char *config_filename) +{ + +  struct RevokeState *rs; + +  rs = GNUNET_new (struct RevokeState); +  rs->expected_response_code = expected_response_code; +  rs->coin_reference = coin_reference; +  rs->config_filename = config_filename; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = rs, +      .label = label, +      .run = &revoke_run, +      .cleanup = &revoke_cleanup, +      .traits = &revoke_traits +    }; + +    return cmd; +  } +} diff --git a/src/testing/testing_api_cmd_refresh.c b/src/testing/testing_api_cmd_refresh.c new file mode 100644 index 00000000..73b74daf --- /dev/null +++ b/src/testing/testing_api_cmd_refresh.c @@ -0,0 +1,1410 @@ +/* +  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_refresh.c + * @brief commands for testing all "refresh" features. + * @author Marcello Stanisci + */ +#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 about a fresh coin generated by the refresh + * operation. + */ +struct TALER_TESTING_FreshCoinData +{ + +  /** +   * 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. +   */ +  const struct TALER_EXCHANGE_DenomPublicKey *pk; + +  /** +   * Set (by the interpreter) to the exchange's signature over the +   * coin's public key. +   */ +  struct TALER_DenominationSignature sig; + +  /** +   * Set (by the interpreter) to the coin's private key. +   */ +  struct TALER_CoinSpendPrivateKeyP coin_priv; + +  /** +   * The blinding key (needed for recoup operations). +   */ +  struct TALER_DenominationBlindingKeyP blinding_key; + +}; + + +/** + * State for a "refresh melt" command. + */ +struct RefreshMeltState +{ + +  /** +   * Reference to reserve_withdraw operations for coin to +   * be used for the /refresh/melt operation. +   */ +  const char *coin_reference; + +  /** +   * "Crypto data" used in the refresh operation. +   */ +  char *refresh_data; + +  /** +   * Reference to a previous melt command. +   */ +  const char *melt_reference; + +  /** +   * Melt handle while operation is running. +   */ +  struct TALER_EXCHANGE_RefreshMeltHandle *rmh; + +  /** +   * Interpreter state. +   */ +  struct TALER_TESTING_Interpreter *is; + +  /** +   * Array of the denomination public keys +   * corresponding to the @e num_fresh_coins; +   */ +  struct TALER_EXCHANGE_DenomPublicKey *fresh_pks; + +  /** +   * Private key of the dirty coin being melted. +   */ +  const struct TALER_CoinSpendPrivateKeyP *melt_priv; + +  /** +   * Task scheduled to try later. +   */ +  struct GNUNET_SCHEDULER_Task *retry_task; + +  /** +   * How long do we wait until we retry? +   */ +  struct GNUNET_TIME_Relative backoff; + +  /** +   * Number of bytes in @e refresh_data. +   */ +  size_t refresh_data_length; + +  /** +   * Amounts to be generated during melt. +   */ +  const char **melt_fresh_amounts; + +  /** +   * Number of fresh coins generated by the melt. +   */ +  unsigned int num_fresh_coins; + +  /** +   * Expected HTTP response code. +   */ +  unsigned int expected_response_code; + +  /** +   * if set to #GNUNET_YES, then two /refresh/melt operations +   * will be performed.  This is needed to trigger the logic +   * that manages those already-made requests.  Note: it +   * is not possible to just copy-and-paste a test refresh melt +   * CMD to have the same effect, because every data preparation +   * generates new planchets that (in turn) make the whole "hash" +   * different from any previous one, therefore NOT allowing the +   * exchange to pick any previous /rerfesh/melt operation from +   * the database. +   */ +  unsigned int double_melt; + +  /** +   * Should we retry on (transient) failures? +   */ +  int do_retry; + +  /** +   * Set by the melt callback as it comes from the exchange. +   */ +  uint16_t noreveal_index; +}; + + +/** + * State for a "refresh reveal" CMD. + */ +struct RefreshRevealState +{ +  /** +   * Link to a "refresh melt" command. +   */ +  const char *melt_reference; + +  /** +   * Reveal handle while operation is running. +   */ +  struct TALER_EXCHANGE_RefreshRevealHandle *rrh; + +  /** +   * 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. +   */ +  struct TALER_TESTING_FreshCoinData *fresh_coins; + +  /** +   * 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; + +  /** +   * Number of fresh coins withdrawn, set by the +   * reveal callback as it comes from the exchange, +   * it is the length of the @e fresh_coins array. +   */ +  unsigned int num_fresh_coins; + +  /** +   * Expected HTTP response code. +   */ +  unsigned int expected_response_code; + +  /** +   * Should we retry on (transient) failures? +   */ +  int do_retry; + +}; + + +/** + * State for a "refresh link" CMD. + */ +struct RefreshLinkState +{ +  /** +   * Link to a "refresh reveal" command. +   */ +  const char *reveal_reference; + +  /** +   * Handle to the ongoing operation. +   */ +  struct TALER_EXCHANGE_RefreshLinkHandle *rlh; + +  /** +   * 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; + +  /** +   * Should we retry on (transient) failures? +   */ +  int do_retry; + +}; + + +/** + * 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); + + +/** + * Task scheduled to re-try #refresh_reveal_run. + * + * @param cls a `struct RefreshRevealState` + */ +static void +do_reveal_retry (void *cls) +{ +  struct RefreshRevealState *rrs = cls; + +  rrs->retry_task = NULL; +  refresh_reveal_run (rrs, +                      NULL, +                      rrs->is); +} + + +/** + * "refresh reveal" request callback; it checks that the response + * code is expected and copies into its command's state the data + * coming from the exchange, namely the fresh coins. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param num_coins number of fresh coins created, length of the + *        @a sigs and @a coin_privs arrays, 0 if the operation + *        failed. + * @param coin_privs array of @a num_coins private keys for the + *        coins that were created, NULL on error. + * @param sigs array of signature over @a num_coins coins, + *        NULL on error. + * @param full_response raw exchange response. + */ +static void +reveal_cb (void *cls, +           unsigned int http_status, +           enum TALER_ErrorCode ec, +           unsigned int num_coins, +           const struct TALER_PlanchetSecretsP *coin_privs, +           const struct TALER_DenominationSignature *sigs, +           const json_t *full_response) +{ +  struct RefreshRevealState *rrs = cls; +  const struct TALER_TESTING_Command *melt_cmd; + +  rrs->rrh = NULL; +  if (rrs->expected_response_code != http_status) +  { +    if (GNUNET_YES == rrs->do_retry) +    { +      if ( (0 == http_status) || +           (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || +           (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                    "Retrying refresh reveal failed with %u/%d\n", +                    http_status, +                    (int) ec); +        /* on DB conflicts, do not use backoff */ +        if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) +          rrs->backoff = GNUNET_TIME_UNIT_ZERO; +        else +          rrs->backoff = EXCHANGE_LIB_BACKOFF (rrs->backoff); +        rrs->retry_task = GNUNET_SCHEDULER_add_delayed (rrs->backoff, +                                                        &do_reveal_retry, +                                                        rrs); +        return; +      } +    } +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u/%d to command %s in %s:%u\n", +                http_status, +                (int) ec, +                rrs->is->commands[rrs->is->ip].label, +                __FILE__, +                __LINE__); +    json_dumpf (full_response, stderr, 0); +    TALER_TESTING_interpreter_fail (rrs->is); +    return; +  } +  melt_cmd = TALER_TESTING_interpreter_lookup_command +               (rrs->is, rrs->melt_reference); +  if (NULL == melt_cmd) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (rrs->is); +    return; +  } +  rrs->num_fresh_coins = num_coins; +  switch (http_status) +  { +  case MHD_HTTP_OK: +    rrs->fresh_coins = GNUNET_new_array (num_coins, +                                         struct TALER_TESTING_FreshCoinData); +    for (unsigned int i = 0; i<num_coins; i++) +    { +      struct TALER_TESTING_FreshCoinData *fc = &rrs->fresh_coins[i]; + +      if (GNUNET_OK != +          TALER_TESTING_get_trait_denom_pub (melt_cmd, +                                             i, +                                             &fc->pk)) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (rrs->is); +        return; +      } +      fc->coin_priv = coin_privs[i].coin_priv; +      fc->blinding_key = coin_privs[i].blinding_key; +      fc->sig.rsa_signature = GNUNET_CRYPTO_rsa_signature_dup +                                (sigs[i].rsa_signature); +    } +    break; +  default: +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Unknown HTTP status %d\n", +                http_status); +  } +  TALER_TESTING_interpreter_next (rrs->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) +{ +  struct RefreshRevealState *rrs = cls; +  struct RefreshMeltState *rms; +  const struct TALER_TESTING_Command *melt_cmd; + +  rrs->is = is; +  melt_cmd = TALER_TESTING_interpreter_lookup_command +               (is, rrs->melt_reference); + +  if (NULL == melt_cmd) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (rrs->is); +    return; +  } +  rms = melt_cmd->cls; +  rrs->rrh = TALER_EXCHANGE_refresh_reveal +               (is->exchange, +               rms->refresh_data_length, +               rms->refresh_data, +               rms->noreveal_index, +               &reveal_cb, rrs); + +  if (NULL == rrs->rrh) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +} + + +/** + * Free the state from a "refresh reveal" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +refresh_reveal_cleanup (void *cls, +                        const struct TALER_TESTING_Command *cmd) +{ +  struct RefreshRevealState *rrs = cls; + +  if (NULL != rrs->rrh) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Command %u (%s) did not complete\n", +                rrs->is->ip, +                cmd->label); + +    TALER_EXCHANGE_refresh_reveal_cancel (rrs->rrh); +    rrs->rrh = NULL; +  } +  if (NULL != rrs->retry_task) +  { +    GNUNET_SCHEDULER_cancel (rrs->retry_task); +    rrs->retry_task = NULL; +  } + +  for (unsigned int j = 0; j < rrs->num_fresh_coins; j++) +    GNUNET_CRYPTO_rsa_signature_free (rrs->fresh_coins[j].sig.rsa_signature); + +  GNUNET_free_non_null (rrs->fresh_coins); +  rrs->fresh_coins = NULL; +  rrs->num_fresh_coins = 0; +  GNUNET_free (rrs); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_link_run (void *cls, +                  const struct TALER_TESTING_Command *cmd, +                  struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #refresh_link_run. + * + * @param cls a `struct RefreshLinkState` + */ +static void +do_link_retry (void *cls) +{ +  struct RefreshLinkState *rls = cls; + +  rls->retry_task = NULL; +  refresh_link_run (rls, +                    NULL, +                    rls->is); +} + + +/** + * "refresh link" operation callback, checks that HTTP response + * code is expected _and_ that all the linked coins were actually + * withdrawn by the "refresh reveal" CMD. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code + * @param num_coins number of fresh coins created, length of the + *        @a sigs and @a coin_privs arrays, 0 if the operation + *        failed. + * @param coin_privs array of @a num_coins private keys for the + *        coins that were created, NULL on error. + * @param sigs array of signature over @a num_coins coins, NULL on + *        error. + * @param pubs array of public keys for the @a sigs, + *        NULL on error. + * @param full_response raw response from the exchange. + */ +static void +link_cb (void *cls, +         unsigned int http_status, +         enum TALER_ErrorCode ec, +         unsigned int num_coins, +         const struct TALER_CoinSpendPrivateKeyP *coin_privs, +         const struct TALER_DenominationSignature *sigs, +         const struct TALER_DenominationPublicKey *pubs, +         const json_t *full_response) +{ + +  struct RefreshLinkState *rls = cls; +  const struct TALER_TESTING_Command *reveal_cmd; +  struct TALER_TESTING_Command *link_cmd +    = &rls->is->commands[rls->is->ip]; +  unsigned int found; +  const unsigned int *num_fresh_coins; + +  rls->rlh = NULL; +  if (rls->expected_response_code != http_status) +  { +    if (GNUNET_YES == rls->do_retry) +    { +      if ( (0 == http_status) || +           (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || +           (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                    "Retrying refresh link failed with %u/%d\n", +                    http_status, +                    (int) ec); +        /* on DB conflicts, do not use backoff */ +        if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) +          rls->backoff = GNUNET_TIME_UNIT_ZERO; +        else +          rls->backoff = EXCHANGE_LIB_BACKOFF (rls->backoff); +        rls->retry_task = GNUNET_SCHEDULER_add_delayed (rls->backoff, +                                                        &do_link_retry, +                                                        rls); +        return; +      } +    } +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u/%d to command %s in %s:%u\n", +                http_status, +                (int) ec, +                link_cmd->label, +                __FILE__, +                __LINE__); +    json_dumpf (full_response, stderr, 0); +    TALER_TESTING_interpreter_fail (rls->is); +    return; +  } +  reveal_cmd = TALER_TESTING_interpreter_lookup_command +                 (rls->is, rls->reveal_reference); + +  if (NULL == reveal_cmd) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (rls->is); +    return; +  } + +  switch (http_status) +  { +  case MHD_HTTP_OK: +    /* check that number of coins returned matches */ +    if (GNUNET_OK != TALER_TESTING_get_trait_uint +          (reveal_cmd, 0, &num_fresh_coins)) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (rls->is); +      return; +    } +    if (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, +                  *num_fresh_coins, +                  __FILE__, +                  __LINE__); +      TALER_TESTING_interpreter_fail (rls->is); +      return; +    } +    /* check that the coins match */ +    for (unsigned int i = 0; i<num_coins; i++) +      for (unsigned int j = i + 1; j<num_coins; j++) +        if (0 == GNUNET_memcmp +              (&coin_privs[i], &coin_privs[j])) +          GNUNET_break (0); +    /* Note: coins might be legitimately permutated in here... */ +    found = 0; + +    /* Will point to the pointer inside the cmd state. */ +    const struct TALER_TESTING_FreshCoinData *fc = NULL; + +    if (GNUNET_OK != TALER_TESTING_get_trait_fresh_coins +          (reveal_cmd, 0, &fc)) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (rls->is); +      return; +    } + +    for (unsigned int i = 0; i<num_coins; i++) +      for (unsigned int j = 0; j<num_coins; j++) +      { +        if ( (0 == GNUNET_memcmp +                (&coin_privs[i], &fc[j].coin_priv)) && +             (0 == GNUNET_CRYPTO_rsa_signature_cmp +                (fc[i].sig.rsa_signature, +                sigs[j].rsa_signature)) && +             (0 == GNUNET_CRYPTO_rsa_public_key_cmp +                (fc[i].pk->key.rsa_public_key, +                pubs[j].rsa_public_key)) ) +        { +          found++; +          break; +        } +      } +    if (found != num_coins) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Only %u/%u coins match expectations\n", +                  found, num_coins); +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (rls->is); +      return; +    } +    break; +  default: +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unknown HTTP response code %u.\n", +                http_status); +  } +  TALER_TESTING_interpreter_next (rls->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_link_run (void *cls, +                  const struct TALER_TESTING_Command *cmd, +                  struct TALER_TESTING_Interpreter *is) +{ +  struct RefreshLinkState *rls = cls; +  struct RefreshRevealState *rrs; +  struct RefreshMeltState *rms; +  const struct TALER_TESTING_Command *reveal_cmd; +  const struct TALER_TESTING_Command *melt_cmd; +  const struct TALER_TESTING_Command *coin_cmd; + +  rls->is = is; +  reveal_cmd = TALER_TESTING_interpreter_lookup_command +                 (rls->is, rls->reveal_reference); + +  if (NULL == reveal_cmd) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (rls->is); +    return; +  } +  rrs = reveal_cmd->cls; +  melt_cmd = TALER_TESTING_interpreter_lookup_command +               (rls->is, rrs->melt_reference); + +  if (NULL == melt_cmd) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (rls->is); +    return; +  } + +  /* find reserve_withdraw command */ +  { +    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; +    } +  } + +  const struct TALER_CoinSpendPrivateKeyP *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); +    return; +  } + +  /* finally, use private key from withdraw sign command */ +  rls->rlh = TALER_EXCHANGE_refresh_link +               (is->exchange, coin_priv, &link_cb, rls); + +  if (NULL == rls->rlh) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (rls->is); +    return; +  } +} + + +/** + * Free the state of the "refresh link" CMD, and possibly + * cancel a operation thereof. + * + * @param cls closure + * @param cmd the command which is being cleaned up. + */ +static void +refresh_link_cleanup (void *cls, +                      const struct TALER_TESTING_Command *cmd) +{ +  struct RefreshLinkState *rls = cls; + +  if (NULL != rls->rlh) +  { + +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Command %u (%s) did not complete\n", +                rls->is->ip, +                cmd->label); +    TALER_EXCHANGE_refresh_link_cancel (rls->rlh); +    rls->rlh = NULL; +  } +  if (NULL != rls->retry_task) +  { +    GNUNET_SCHEDULER_cancel (rls->retry_task); +    rls->retry_task = NULL; +  } +  GNUNET_free (rls); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_melt_run (void *cls, +                  const struct TALER_TESTING_Command *cmd, +                  struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #refresh_melt_run. + * + * @param cls a `struct RefreshMeltState` + */ +static void +do_melt_retry (void *cls) +{ +  struct RefreshMeltState *rms = cls; + +  rms->retry_task = NULL; +  refresh_melt_run (rms, +                    NULL, +                    rms->is); +} + + +/** + * Callback for a "refresh melt" operation; checks if the HTTP + * response code is okay and re-run the melt operation if the + * CMD was set to do so. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param noreveal_index choice by the exchange in the + *        cut-and-choose protocol, UINT16_MAX on error. + * @param exchange_pub public key the exchange used for signing. + * @param full_response raw response body from the exchange. + */ +static void +melt_cb (void *cls, +         unsigned int http_status, +         enum TALER_ErrorCode ec, +         uint32_t noreveal_index, +         const struct TALER_ExchangePublicKeyP *exchange_pub, +         const json_t *full_response) +{ +  struct RefreshMeltState *rms = cls; + +  rms->rmh = NULL; +  if (rms->expected_response_code != http_status) +  { +    if (GNUNET_YES == rms->do_retry) +    { +      if ( (0 == http_status) || +           (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || +           (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                    "Retrying refresh melt failed with %u/%d\n", +                    http_status, +                    (int) ec); +        /* on DB conflicts, do not use backoff */ +        if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) +          rms->backoff = GNUNET_TIME_UNIT_ZERO; +        else +          rms->backoff = EXCHANGE_LIB_BACKOFF (rms->backoff); +        rms->retry_task = GNUNET_SCHEDULER_add_delayed +                            (rms->backoff, +                            &do_melt_retry, +                            rms); +        return; +      } +    } +    GNUNET_log +      (GNUNET_ERROR_TYPE_ERROR, +      "Unexpected response code %u/%d to command %s in %s:%u\n", +      http_status, +      (int) ec, +      rms->is->commands[rms->is->ip].label, +      __FILE__, +      __LINE__); +    json_dumpf (full_response, stderr, 0); +    TALER_TESTING_interpreter_fail (rms->is); +    return; +  } +  rms->noreveal_index = noreveal_index; + +  if (GNUNET_YES == rms->double_melt) +  { +    TALER_LOG_DEBUG ("Doubling the melt (%s)\n", +                     rms->is->commands[rms->is->ip].label); +    rms->rmh = TALER_EXCHANGE_refresh_melt +                 (rms->is->exchange, rms->refresh_data_length, +                 rms->refresh_data, &melt_cb, rms); +    rms->double_melt = GNUNET_NO; +    return; +  } +  TALER_TESTING_interpreter_next (rms->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refresh_melt_run (void *cls, +                  const struct TALER_TESTING_Command *cmd, +                  struct TALER_TESTING_Interpreter *is) +{ +  struct RefreshMeltState *rms = cls; +  unsigned int num_fresh_coins; +  const char *default_melt_fresh_amounts[] = { +    "EUR:1", "EUR:1", "EUR:1", "EUR:0.1", +    NULL +  }; +  const char **melt_fresh_amounts; + +  if (NULL == (melt_fresh_amounts = rms->melt_fresh_amounts)) +    melt_fresh_amounts = default_melt_fresh_amounts; +  rms->is = is; +  rms->noreveal_index = UINT16_MAX; +  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); +  { +    struct TALER_Amount melt_amount; +    struct TALER_Amount fresh_amount; +    const struct TALER_DenominationSignature *melt_sig; +    const struct TALER_EXCHANGE_DenomPublicKey *melt_denom_pub; +    const struct TALER_TESTING_Command *coin_command; + +    if (NULL == (coin_command +                   = TALER_TESTING_interpreter_lookup_command +                       (is, rms->coin_reference))) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (rms->is); +      return; +    } + +    if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv +          (coin_command, 0, &rms->melt_priv)) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (rms->is); +      return; +    } + +    if (GNUNET_OK != +        TALER_TESTING_get_trait_denom_sig (coin_command, +                                           0, +                                           &melt_sig)) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (rms->is); +      return; +    } +    if (GNUNET_OK != TALER_TESTING_get_trait_denom_pub +          (coin_command, 0, &melt_denom_pub)) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (rms->is); +      return; +    } +    /* Melt amount starts with the melt fee of the old coin; we'll add the +       values and withdraw fees of the fresh coins next */ +    melt_amount = melt_denom_pub->fee_refresh; +    for (unsigned int i = 0; i<num_fresh_coins; i++) +    { +      const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk; + +      if (GNUNET_OK != TALER_string_to_amount +            (melt_fresh_amounts[i], &fresh_amount)) +      { +        GNUNET_break (0); +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Failed to parse amount `%s' at index %u\n", +                    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); +      if (NULL == fresh_pk) +      { +        GNUNET_break (0); +        /* Subroutine logs specific error */ +        TALER_TESTING_interpreter_fail (rms->is); +        return; +      } +      GNUNET_assert (GNUNET_OK == +                     TALER_amount_add (&melt_amount, +                                       &melt_amount, +                                       &fresh_amount)); +      GNUNET_assert (GNUNET_OK == +                     TALER_amount_add (&melt_amount, +                                       &melt_amount, +                                       &fresh_pk->fee_withdraw)); +      rms->fresh_pks[i] = *fresh_pk; +      /* Make a deep copy of the RSA key */ +      rms->fresh_pks[i].key.rsa_public_key +        = GNUNET_CRYPTO_rsa_public_key_dup (fresh_pk->key.rsa_public_key); +    } +    rms->refresh_data +      = TALER_EXCHANGE_refresh_prepare (rms->melt_priv, +                                        &melt_amount, +                                        melt_sig, +                                        melt_denom_pub, +                                        num_fresh_coins, +                                        rms->fresh_pks, +                                        &rms->refresh_data_length); + +    if (NULL == rms->refresh_data) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (rms->is); +      return; +    } +    rms->rmh = TALER_EXCHANGE_refresh_melt (is->exchange, +                                            rms->refresh_data_length, +                                            rms->refresh_data, +                                            &melt_cb, +                                            rms); + +    if (NULL == rms->rmh) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (rms->is); +      return; +    } +  } +} + + +/** + * Free the "refresh melt" CMD state, and possibly cancel a + * pending operation thereof. + * + * @param cls closure, must be a `struct RefreshMeltState`. + * @param cmd the command which is being cleaned up. + */ +static void +refresh_melt_cleanup (void *cls, +                      const struct TALER_TESTING_Command *cmd) +{ +  struct RefreshMeltState *rms = cls; + +  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_EXCHANGE_refresh_melt_cancel (rms->rmh); +    rms->rmh = NULL; +  } +  if (NULL != rms->retry_task) +  { +    GNUNET_SCHEDULER_cancel (rms->retry_task); +    rms->retry_task = NULL; +  } +  if (NULL != rms->fresh_pks) +  { +    for (unsigned int i = 0; i < rms->num_fresh_coins; i++) +      GNUNET_CRYPTO_rsa_public_key_free (rms->fresh_pks[i].key.rsa_public_key); +  } +  GNUNET_free_non_null (rms->fresh_pks); +  rms->fresh_pks = NULL; +  GNUNET_free_non_null (rms->refresh_data); +  rms->refresh_data = NULL; +  rms->refresh_data_length = 0; +  GNUNET_free_non_null (rms->melt_fresh_amounts); +  GNUNET_free (rms); +} + + +/** + * Offer internal data to the "refresh melt" CMD. + * + * @param cls closure. + * @param[out] ret result (could be anything). + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +refresh_melt_traits (void *cls, +                     const void **ret, +                     const char *trait, +                     unsigned int index) +{ +  struct RefreshMeltState *rms = cls; + +  if (index >= rms->num_fresh_coins) +  { +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } +  { +    struct TALER_TESTING_Trait traits[] = { +      TALER_TESTING_make_trait_denom_pub (index, &rms->fresh_pks[index]), +      TALER_TESTING_make_trait_coin_priv (0, rms->melt_priv), +      TALER_TESTING_trait_end () +    }; + +    return TALER_TESTING_get_trait (traits, +                                    ret, +                                    trait, +                                    index); +  } +} + + +/** + * Parse list of amounts for melt operation. + * + * @param[in,out] rms where to store the list + * @param ap NULL-termianted list of amounts to be melted (one per fresh coin) + * @return #GNUNET_OK on success + */ +static int +parse_amounts (struct RefreshMeltState *rms, +               va_list ap) +{ +  unsigned int len; +  unsigned int off; +  const char *amount; + +  len = 0; +  off = 0; +  while (NULL != (amount = va_arg (ap, const char *))) +  { +    if (len == off) +    { +      struct TALER_Amount a; + +      GNUNET_array_grow (rms->melt_fresh_amounts, +                         len, +                         off + 16); +      if (GNUNET_OK != +          TALER_string_to_amount (amount, &a)) +      { +        GNUNET_break (0); +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Failed to parse amount `%s' at index %u\n", +                    amount, off); +        GNUNET_free (rms->melt_fresh_amounts); +        rms->melt_fresh_amounts = NULL; +        return GNUNET_SYSERR; +      } +      rms->melt_fresh_amounts[off++] = amount; +    } +  } +  if (0 == off) +    return GNUNET_OK; /* no amounts given == use defaults! */ +  /* ensure NULL-termination */ +  GNUNET_array_grow (rms->melt_fresh_amounts, +                     len, +                     off + 1); +  return GNUNET_OK; +} + + +/** + * Create a "refresh melt" command. + * + * @param label command label. + * @param coin_reference reference to a command + *        that will provide a coin to refresh. + * @param expected_response_code expected HTTP code. + * @param ... NULL-terminated list of amounts to be melted + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_melt (const char *label, +                                const char *coin_reference, +                                unsigned int expected_response_code, +                                ...) +{ +  struct RefreshMeltState *rms; +  va_list ap; + +  rms = GNUNET_new (struct RefreshMeltState); +  rms->coin_reference = coin_reference; +  rms->expected_response_code = expected_response_code; +  va_start (ap, expected_response_code); +  GNUNET_assert (GNUNET_OK == +                 parse_amounts (rms, ap)); +  va_end (ap); +  { +    struct TALER_TESTING_Command cmd = { +      .label = label, +      .cls = rms, +      .run = &refresh_melt_run, +      .cleanup = &refresh_melt_cleanup, +      .traits = &refresh_melt_traits +    }; + +    return cmd; +  } +} + + +/** + * Create a "refresh melt" CMD that does TWO /refresh/melt + * requests.  This was needed to test the replay of a valid melt + * request, see #5312. + * + * @param label command label + * @param coin_reference reference to a command that will provide + *        a coin to refresh + * @param expected_response_code expected HTTP code + * @param ... NULL-terminated list of amounts to be melted + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_melt_double (const char *label, +                                       const char *coin_reference, +                                       unsigned int expected_response_code, +                                       ...) +{ +  struct RefreshMeltState *rms; +  va_list ap; + +  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); +  GNUNET_assert (GNUNET_OK == +                 parse_amounts (rms, ap)); +  va_end (ap); +  { +    struct TALER_TESTING_Command cmd = { +      .label = label, +      .cls = rms, +      .run = &refresh_melt_run, +      .cleanup = &refresh_melt_cleanup, +      .traits = &refresh_melt_traits +    }; + +    return cmd; +  } +} + + +/** + * Modify a "refresh melt" command to enable retries. + * + * @param cmd command + * @return modified command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_melt_with_retry (struct TALER_TESTING_Command cmd) +{ +  struct RefreshMeltState *rms; + +  GNUNET_assert (&refresh_melt_run == cmd.run); +  rms = cmd.cls; +  rms->do_retry = GNUNET_YES; +  return cmd; +} + + +/** + * Offer internal data from a "refresh reveal" CMD. + * + * @param cls closure. + * @param[out] ret result (could be anything). + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +refresh_reveal_traits (void *cls, +                       const void **ret, +                       const char *trait, +                       unsigned int index) +{ +  struct RefreshRevealState *rrs = cls; +  unsigned int num_coins = rrs->num_fresh_coins; +#define NUM_TRAITS ((num_coins * 4) + 3) +  struct TALER_TESTING_Trait traits[NUM_TRAITS]; + +  /* Making coin privs traits */ +  for (unsigned int i = 0; i<num_coins; i++) +    traits[i] = TALER_TESTING_make_trait_coin_priv +                  (i, &rrs->fresh_coins[i].coin_priv); + +  /* Making denom pubs traits */ +  for (unsigned int i = 0; i<num_coins; i++) +    traits[num_coins + i] +      = TALER_TESTING_make_trait_denom_pub +          (i, rrs->fresh_coins[i].pk); + +  /* Making denom sigs traits */ +  for (unsigned int i = 0; i<num_coins; i++) +    traits[(num_coins * 2) + i] +      = TALER_TESTING_make_trait_denom_sig +          (i, &rrs->fresh_coins[i].sig); +  /* blinding key traits */ +  for (unsigned int i = 0; i<num_coins; i++) +    traits[(num_coins * 3) + i] +      = TALER_TESTING_make_trait_blinding_key (i, +                                               &rrs->fresh_coins[i].blinding_key), + +    /* number of fresh coins */ +    traits[(num_coins * 4)] = TALER_TESTING_make_trait_uint +                                (0, &rrs->num_fresh_coins); + +  /* whole array of fresh coins */ +  traits[(num_coins * 4) + 1] +    = TALER_TESTING_make_trait_fresh_coins (0, rrs->fresh_coins), + +  /* end of traits */ +  traits[(num_coins * 4) + 2] = TALER_TESTING_trait_end (); + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Create a "refresh reveal" command. + * + * @param label command label. + * @param melt_reference reference to a "refresh melt" command. + * @param expected_response_code expected HTTP response code. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_reveal (const char *label, +                                  const char *melt_reference, +                                  unsigned int expected_response_code) +{ +  struct RefreshRevealState *rrs; + +  rrs = GNUNET_new (struct RefreshRevealState); +  rrs->melt_reference = melt_reference; +  rrs->expected_response_code = expected_response_code; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = rrs, +      .label = label, +      .run = &refresh_reveal_run, +      .cleanup = &refresh_reveal_cleanup, +      .traits = &refresh_reveal_traits +    }; + +    return cmd; +  } +} + + +/** + * Modify a "refresh reveal" command to enable retries. + * + * @param cmd command + * @return modified command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_reveal_with_retry (struct TALER_TESTING_Command cmd) +{ +  struct RefreshRevealState *rrs; + +  GNUNET_assert (&refresh_reveal_run == cmd.run); +  rrs = cmd.cls; +  rrs->do_retry = GNUNET_YES; +  return cmd; +} + + +/** + * Create a "refresh link" command. + * + * @param label command label. + * @param reveal_reference reference to a "refresh reveal" CMD. + * @param expected_response_code expected HTTP response code + * @return the "refresh link" command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_link (const char *label, +                                const char *reveal_reference, +                                unsigned int expected_response_code) +{ +  struct RefreshLinkState *rrs; + +  rrs = GNUNET_new (struct RefreshLinkState); +  rrs->reveal_reference = reveal_reference; +  rrs->expected_response_code = expected_response_code; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = rrs, +      .label = label, +      .run = &refresh_link_run, +      .cleanup = &refresh_link_cleanup +    }; + +    return cmd; +  } +} + + +/** + * Modify a "refresh link" command to enable retries. + * + * @param cmd command + * @return modified command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refresh_link_with_retry (struct TALER_TESTING_Command cmd) +{ +  struct RefreshLinkState *rls; + +  GNUNET_assert (&refresh_link_run == cmd.run); +  rls = cmd.cls; +  rls->do_retry = GNUNET_YES; +  return cmd; +} diff --git a/src/testing/testing_api_cmd_refund.c b/src/testing/testing_api_cmd_refund.c new file mode 100644 index 00000000..0150086e --- /dev/null +++ b/src/testing/testing_api_cmd_refund.c @@ -0,0 +1,332 @@ +/* +  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 testing/testing_api_cmd_refund.c + * @brief Implement the /refund test command, plus other + *        corollary commands (?). + * @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 "refund" CMD. + */ +struct RefundState +{ +  /** +   * Expected HTTP response code. +   */ +  unsigned int expected_response_code; + +  /** +   * Amount to be refunded. +   */ +  const char *refund_amount; + +  /** +   * Expected refund fee. +   */ +  const char *refund_fee; + +  /** +   * Reference to any command that can provide a coin to refund. +   */ +  const char *coin_reference; + +  /** +   * Refund transaction identifier. +   */ +  uint64_t refund_transaction_id; + +  /** +   * Connection to the exchange. +   */ +  struct TALER_EXCHANGE_Handle *exchange; + +  /** +   * Handle to the refund operation. +   */ +  struct TALER_EXCHANGE_RefundHandle *rh; + +  /** +   * Interpreter state. +   */ +  struct TALER_TESTING_Interpreter *is; +}; + + +/** + * Check the result for the refund request, just check if the + * response code is acceptable. + * + * @param cls closure + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param exchange_pub public key the exchange + *        used for signing @a obj. + * @param obj response object. + */ +static void +refund_cb (void *cls, +           unsigned int http_status, +           enum TALER_ErrorCode ec, +           const struct TALER_ExchangePublicKeyP *exchange_pub, +           const json_t *obj) +{ + +  struct RefundState *rs = cls; +  struct TALER_TESTING_Command *refund_cmd; + +  refund_cmd = &rs->is->commands[rs->is->ip]; +  rs->rh = NULL; +  if (rs->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s in %s:%u\n", +                http_status, +                refund_cmd->label, +                __FILE__, +                __LINE__); +    json_dumpf (obj, stderr, 0); +    TALER_TESTING_interpreter_fail (rs->is); +    return; +  } +  TALER_TESTING_interpreter_next (rs->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +refund_run (void *cls, +            const struct TALER_TESTING_Command *cmd, +            struct TALER_TESTING_Interpreter *is) +{ +  struct RefundState *rs = cls; +  const struct TALER_CoinSpendPrivateKeyP *coin_priv; +  struct TALER_CoinSpendPublicKeyP coin; +  const json_t *contract_terms; +  struct GNUNET_HashCode h_contract_terms; +  struct TALER_Amount refund_fee; +  struct TALER_Amount refund_amount; +  const struct TALER_MerchantPrivateKeyP *merchant_priv; +  const struct TALER_TESTING_Command *coin_cmd; + +  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", +                rs->refund_amount, +                is->ip, +                cmd->label); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  if (GNUNET_OK != +      TALER_string_to_amount (rs->refund_fee, +                              &refund_fee)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to parse amount `%s' at %u/%s\n", +                rs->refund_fee, +                is->ip, +                cmd->label); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  coin_cmd = TALER_TESTING_interpreter_lookup_command (is, +                                                       rs->coin_reference); +  if (NULL == coin_cmd) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  if (GNUNET_OK != +      TALER_TESTING_get_trait_contract_terms (coin_cmd, +                                              0, +                                              &contract_terms)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  GNUNET_assert (GNUNET_OK == +                 TALER_JSON_hash (contract_terms, +                                  &h_contract_terms)); + +  /* Hunting for a coin .. */ +  if (GNUNET_OK != +      TALER_TESTING_get_trait_coin_priv (coin_cmd, +                                         0, +                                         &coin_priv)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, +                                      &coin.eddsa_pub); +  if (GNUNET_OK != +      TALER_TESTING_get_trait_merchant_priv (coin_cmd, +                                             0, +                                             &merchant_priv)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  rs->rh = TALER_EXCHANGE_refund (rs->exchange, +                                  &refund_amount, +                                  &refund_fee, +                                  &h_contract_terms, +                                  &coin, +                                  rs->refund_transaction_id, +                                  merchant_priv, +                                  &refund_cb, +                                  rs); +  GNUNET_assert (NULL != rs->rh); +} + + +/** + * Free the state from a "refund" CMD, and possibly cancel + * a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +refund_cleanup (void *cls, +                const struct TALER_TESTING_Command *cmd) +{ +  struct RefundState *rs = cls; + +  if (NULL != rs->rh) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Command %u (%s) did not complete\n", +                rs->is->ip, +                cmd->label); +    TALER_EXCHANGE_refund_cancel (rs->rh); +    rs->rh = NULL; +  } +  GNUNET_free (rs); +} + + +/** + * Create a "refund" command. + * + * @param label command label. + * @param expected_response_code expected HTTP status code. + * @param refund_amount the amount to ask a refund for. + * @param refund_fee expected refund fee. + * @param coin_reference reference to a command that can + *        provide a coin to be refunded. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refund (const char *label, +                          unsigned int expected_response_code, +                          const char *refund_amount, +                          const char *refund_fee, +                          const char *coin_reference) +{ +  struct RefundState *rs; + +  rs = GNUNET_new (struct RefundState); + +  rs->expected_response_code = expected_response_code; +  rs->refund_amount = refund_amount; +  rs->refund_fee = refund_fee; +  rs->coin_reference = coin_reference; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = rs, +      .label = label, +      .run = &refund_run, +      .cleanup = &refund_cleanup +    }; + +    return cmd; +  } +} + + +/** + * Create a "refund" command, allow to specify refund transaction + * id.  Mainly used to create conflicting requests. + * + * @param label command label. + * @param expected_response_code expected HTTP status code. + * @param refund_amount the amount to ask a refund for. + * @param refund_fee expected refund fee. + * @param coin_reference reference to a command that can + *        provide a coin to be refunded. + * @param refund_transaction_id transaction id to use + *        in the request. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_refund_with_id +  (const char *label, +  unsigned int expected_response_code, +  const char *refund_amount, +  const char *refund_fee, +  const char *coin_reference, +  uint64_t refund_transaction_id) +{ +  struct RefundState *rs; + +  rs = GNUNET_new (struct RefundState); +  rs->expected_response_code = expected_response_code; +  rs->refund_amount = refund_amount; +  rs->refund_fee = refund_fee; +  rs->coin_reference = coin_reference; +  rs->refund_transaction_id = refund_transaction_id; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = rs, +      .label = label, +      .run = &refund_run, +      .cleanup = &refund_cleanup +    }; + +    return cmd; +  } +} diff --git a/src/testing/testing_api_cmd_serialize_keys.c b/src/testing/testing_api_cmd_serialize_keys.c new file mode 100644 index 00000000..296a2ddc --- /dev/null +++ b/src/testing/testing_api_cmd_serialize_keys.c @@ -0,0 +1,295 @@ +/* +  This file is part of TALER +  (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as +  published by the Free Software Foundation; either version 3, or +  (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_cmd_serialize_keys.c + * @brief Lets tests use the keys serialization API. + * @author Marcello Stanisci + */ +#include "platform.h" +#include <jansson.h> +#include "taler_testing_lib.h" + + +/** + * Internal state for a serialize-keys CMD. + */ +struct SerializeKeysState +{ +  /** +   * Serialized keys. +   */ +  json_t *keys; + +  /** +   * Exchange URL.  Needed because the exchange gets disconnected +   * from, after keys serialization.  This value is then needed by +   * subsequent commands that have to reconnect to the exchagne. +   */ +  char *exchange_url; +}; + + +/** + * Internal state for a connect-with-state CMD. + */ +struct ConnectWithStateState +{ + +  /** +   * Reference to a CMD that offers a serialized key-state +   * that will be used in the reconnection. +   */ +  const char *state_reference; + +  /** +   * If set to GNUNET_YES, then the /keys callback has already +   * been passed the control to the next CMD.  This is necessary +   * because it is not uncommon that the /keys callback gets +   * invoked multiple times, and without this flag, we would keep +   * going "next" CMD upon every invocation (causing impredictable +   * behaviour as for the instruction pointer.) +   */ +  unsigned int consumed; + +  /** +   * Interpreter state. +   */ +  struct TALER_TESTING_Interpreter *is; +}; + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +serialize_keys_run (void *cls, +                    const struct TALER_TESTING_Command *cmd, +                    struct TALER_TESTING_Interpreter *is) +{ +  struct SerializeKeysState *sks = cls; + +  sks->keys = TALER_EXCHANGE_serialize_data (is->exchange); +  if (NULL == sks->keys) +    TALER_TESTING_interpreter_fail (is); + +  sks->exchange_url = GNUNET_strdup +                        (TALER_EXCHANGE_get_base_url (is->exchange)); +  TALER_EXCHANGE_disconnect (is->exchange); +  is->exchange = NULL; +  is->working = GNUNET_NO; +  TALER_TESTING_interpreter_next (is); +} + + +/** + * Cleanup the state of a "serialize keys" CMD. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +serialize_keys_cleanup (void *cls, +                        const struct TALER_TESTING_Command *cmd) +{ +  struct SerializeKeysState *sks = cls; + +  if (NULL != sks->keys) +  { +    json_decref (sks->keys); +  } +  GNUNET_free_non_null (sks->exchange_url); +  GNUNET_free (sks); +} + + +/** + * Offer serialized keys as trait. + * + * @param cls closure. + * @param[out] ret result. + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +serialize_keys_traits (void *cls, +                       const void **ret, +                       const char *trait, +                       unsigned int index) +{ +  struct SerializeKeysState *sks = cls; +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_make_trait_exchange_keys (0, sks->keys), +    TALER_TESTING_make_trait_url (TALER_TESTING_UT_EXCHANGE_BASE_URL, +                                  sks->exchange_url), +    TALER_TESTING_trait_end () +  }; + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +connect_with_state_run (void *cls, +                        const struct TALER_TESTING_Command *cmd, +                        struct TALER_TESTING_Interpreter *is) +{ +  struct ConnectWithStateState *cwss = cls; +  const struct TALER_TESTING_Command *state_cmd; +  const json_t *serialized_keys; +  const char *exchange_url; + +  /* This command usually gets rescheduled after serialized +   * reconnection.  */ +  if (GNUNET_YES == cwss->consumed) +  { +    TALER_TESTING_interpreter_next (is); +    return; +  } + +  cwss->is = is; +  state_cmd = TALER_TESTING_interpreter_lookup_command +                (is, cwss->state_reference); + +  /* Command providing serialized keys not found.  */ +  if (NULL == state_cmd) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  GNUNET_assert (GNUNET_OK == +                 TALER_TESTING_get_trait_exchange_keys (state_cmd, +                                                        0, +                                                        &serialized_keys)); +  { +    char *dump; + +    dump = json_dumps (serialized_keys, +                       JSON_INDENT (1)); +    TALER_LOG_DEBUG ("Serialized key-state: %s\n", +                     dump); +    free (dump); +  } + +  GNUNET_assert (GNUNET_OK == +                 TALER_TESTING_get_trait_url (state_cmd, +                                              TALER_TESTING_UT_EXCHANGE_BASE_URL, +                                              &exchange_url)); +  is->exchange = TALER_EXCHANGE_connect (is->ctx, +                                         exchange_url, +                                         TALER_TESTING_cert_cb, +                                         cwss, +                                         TALER_EXCHANGE_OPTION_DATA, +                                         serialized_keys, +                                         TALER_EXCHANGE_OPTION_END); +  cwss->consumed = GNUNET_YES; +} + + +/** + * Cleanup the state of a "connect with state" CMD.  Just + * a placeholder to avoid jumping on an invalid address. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +connect_with_state_cleanup (void *cls, +                            const struct TALER_TESTING_Command *cmd) +{ +  struct ConnectWithStateState *cwss = cls; + +  GNUNET_free (cwss); +} + + +/** + * Make a serialize-keys CMD.  It will ask for + * keys serialization __and__ disconnect from the + * exchange. + * + * @param label CMD label + * @return the CMD. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_serialize_keys (const char *label) +{ +  struct SerializeKeysState *sks; + +  sks = GNUNET_new (struct SerializeKeysState); +  { +    struct TALER_TESTING_Command cmd = { +      .cls = sks, +      .label = label, +      .run = serialize_keys_run, +      .cleanup = serialize_keys_cleanup, +      .traits = serialize_keys_traits +    }; + +    return cmd; +  } +} + + +/** + * Make a connect-with-state CMD.  This command + * will use a serialized key state to reconnect + * to the exchange. + * + * @param label command label + * @param state_reference label of a CMD offering + *        a serialized key state. + * @return the CMD. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_connect_with_state (const char *label, +                                      const char *state_reference) +{ +  struct ConnectWithStateState *cwss; + +  cwss = GNUNET_new (struct ConnectWithStateState); +  cwss->state_reference = state_reference; +  cwss->consumed = GNUNET_NO; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = cwss, +      .label = label, +      .run = connect_with_state_run, +      .cleanup = connect_with_state_cleanup +    }; + +    return cmd; +  } +} diff --git a/src/testing/testing_api_cmd_signal.c b/src/testing/testing_api_cmd_signal.c new file mode 100644 index 00000000..b2116ebf --- /dev/null +++ b/src/testing/testing_api_cmd_signal.c @@ -0,0 +1,115 @@ +/* +  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_signal.c + * @brief command(s) to send signals to processes. + * @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 "signal" CMD. + */ +struct SignalState +{ +  /** +   * The process to send the signal to. +   */ +  struct GNUNET_OS_Process *process; + +  /** +   * The signal to send to the process. +   */ +  int signal; +}; + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +signal_run (void *cls, +            const struct TALER_TESTING_Command *cmd, +            struct TALER_TESTING_Interpreter *is) +{ +  struct SignalState *ss = cls; + +  GNUNET_break (0 == GNUNET_OS_process_kill +                  (ss->process, ss->signal)); +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "Signaling '%d'..\n", +              ss->signal); +  sleep (6); +  TALER_TESTING_interpreter_next (is); +} + + +/** + * Cleanup the state from a "signal" CMD. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +signal_cleanup (void *cls, +                const struct TALER_TESTING_Command *cmd) +{ +  struct SignalState *ss = cls; + +  GNUNET_free (ss); +} + + +/** + * Create a "signal" CMD. + * + * @param label command label. + * @param process handle to the process to signal. + * @param signal signal to send. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_signal (const char *label, +                          struct GNUNET_OS_Process *process, +                          int signal) +{ +  struct SignalState *ss; + +  ss = GNUNET_new (struct SignalState); +  ss->process = process; +  ss->signal = signal; + + +  struct TALER_TESTING_Command cmd = { +    .cls = ss, +    .label = label, +    .run = &signal_run, +    .cleanup = &signal_cleanup +  }; + +  return cmd; +} diff --git a/src/testing/testing_api_cmd_sleep.c b/src/testing/testing_api_cmd_sleep.c new file mode 100644 index 00000000..91c13a1e --- /dev/null +++ b/src/testing/testing_api_cmd_sleep.c @@ -0,0 +1,133 @@ +/* +  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_sleep.c + * @brief command(s) to sleep for a bit + * @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 "sleep" CMD. + */ +struct SleepState +{ + +  /** +   * How long should we sleep? +   */ +  unsigned int duration; +}; + + +/** + * No traits to offer, just provide a stub to be called when + * some CMDs iterates through the list of all the commands. + * + * @param cls closure. + * @param[out] ret result. + * @param trait name of the trait. + * @param index index number of the trait to return. + * @return #GNUNET_OK on success. + */ +static int +sleep_traits (void *cls, +              const void **ret, +              const char *trait, +              unsigned int index) +{ +  (void) cls; +  (void) ret; +  (void) trait; +  (void) index; +  return GNUNET_NO; +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +sleep_run (void *cls, +           const struct TALER_TESTING_Command *cmd, +           struct TALER_TESTING_Interpreter *is) +{ +  struct SleepState *ss = cls; + +  sleep (ss->duration); +  TALER_TESTING_interpreter_next (is); +} + + +/** + * Cleanup the state from a "sleep" CMD. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +sleep_cleanup (void *cls, +               const struct TALER_TESTING_Command *cmd) +{ +  struct SleepState *ss = cls; + +  (void) cmd; +  GNUNET_free (ss); +} + + +/** + * Sleep for @a duration_s seconds. + * + * @param label command label. + * @param duration_s number of seconds to sleep + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_sleep (const char *label, +                         unsigned int duration_s) +{ +  struct SleepState *ss; + +  ss = GNUNET_new (struct SleepState); +  ss->duration = duration_s; + +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ss, +      .label = label, +      .run = &sleep_run, +      .cleanup = &sleep_cleanup, +      .traits = &sleep_traits +    }; + +    return cmd; +  } +} + + +/* end of testing_api_cmd_sleep.c  */ diff --git a/src/testing/testing_api_cmd_status.c b/src/testing/testing_api_cmd_status.c new file mode 100644 index 00000000..1c652b6d --- /dev/null +++ b/src/testing/testing_api_cmd_status.c @@ -0,0 +1,237 @@ +/* +  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_ReserveStatusHandle *rsh; + +  /** +   * Expected reserve balance. +   */ +  const char *expected_balance; + +  /** +   * 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 http_status HTTP response code. + * @param ec taler-specific error code. + * @param json original JSON response from the exchange + * @param balance current balance in the reserve, NULL on error. + * @param history_length number of entries in the transaction + *        history, 0 on error. + * @param history detailed transaction history, NULL on error. + */ +static void +reserve_status_cb (void *cls, +                   unsigned int http_status, +                   enum TALER_ErrorCode ec, +                   const json_t *json, +                   const struct TALER_Amount *balance, +                   unsigned int history_length, +                   const struct TALER_EXCHANGE_ReserveHistory *history) +{ +  struct StatusState *ss = cls; +  struct TALER_Amount eb; + +  ss->rsh = NULL; +  if (ss->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected HTTP response code: %d in %s:%u\n", +                http_status, +                __FILE__, +                __LINE__); +    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; +  } + +  /** +   * TODO (#6049): We should check that reserve history is consistent.  Every +   * command which relates to reserve 'x' should be added in a linked list of +   * all commands that relate to the same reserve 'x'. +   * +   * API-wise, any command that relates to a reserve should offer a +   * method called e.g. "compare_with_history" that takes an element +   * of the array returned by "/reserve/status" and checks if that +   * element correspond to itself (= the command exposing the check- +   * method). +   * +   * IDEA: Maybe realize this via another trait, some kind of +   * "reserve history update trait" which returns information about +   * how the command changes the history (provided only by commands +   * that change reserve balances)? +   */// +  TALER_TESTING_interpreter_next (ss->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 struct TALER_ReservePublicKeyP *reserve_pubp; + +  ss->is = is; +  create_reserve +    = TALER_TESTING_interpreter_lookup_command (is, +                                                ss->reserve_reference); + +  if (NULL == create_reserve) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  if (GNUNET_OK != +      TALER_TESTING_get_trait_reserve_pub (create_reserve, +                                           0, +                                           &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_reserve_status (is->exchange, +                                           reserve_pubp, +                                           &reserve_status_cb, +                                           ss); +} + + +/** + * Cleanup the state from a "reserve status" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +status_cleanup (void *cls, +                const struct TALER_TESTING_Command *cmd) +{ +  struct StatusState *ss = cls; + +  if (NULL != ss->rsh) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Command %u (%s) did not complete\n", +                ss->is->ip, +                cmd->label); +    TALER_EXCHANGE_reserve_status_cancel (ss->rsh); +    ss->rsh = NULL; +  } +  GNUNET_free (ss); +} + + +/** + * Create a "reserve status" command. + * + * @param label the command label. + * @param reserve_reference reference to the reserve to check. + * @param expected_balance expected balance for the reserve. + * @param expected_response_code expected HTTP response code. + * + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_status (const char *label, +                          const char *reserve_reference, +                          const char *expected_balance, +                          unsigned int expected_response_code) +{ +  struct StatusState *ss; + +  GNUNET_assert (NULL != reserve_reference); +  ss = GNUNET_new (struct StatusState); +  ss->reserve_reference = reserve_reference; +  ss->expected_balance = expected_balance; +  ss->expected_response_code = expected_response_code; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ss, +      .label = label, +      .run = &status_run, +      .cleanup = &status_cleanup +    }; + +    return cmd; +  } +} diff --git a/src/testing/testing_api_cmd_track.c b/src/testing/testing_api_cmd_track.c new file mode 100644 index 00000000..e5c7160f --- /dev/null +++ b/src/testing/testing_api_cmd_track.c @@ -0,0 +1,805 @@ +/* +  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_track.c + * @brief Implement the testing CMDs for the /track operations. + * @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 "track transaction" CMD. + */ +struct TrackTransactionState +{ + +  /** +   * If non NULL, will provide a WTID to be compared against +   * the one returned by the "track transaction" operation. +   */ +  const char *bank_transfer_reference; + +  /** +   * The WTID associated by the transaction being tracked. +   */ +  struct TALER_WireTransferIdentifierRawP wtid; + +  /** +   * Expected HTTP response code. +   */ +  unsigned int expected_response_code; + +  /** +   * Reference to any operation that can provide a transaction. +   * Will be the transaction to track. +   */ +  const char *transaction_reference; + +  /** +   * Index of the coin involved in the transaction.  Recall: +   * at the exchange, the tracking is done _per coin_. +   */ +  unsigned int coin_index; + +  /** +   * Handle to the "track transaction" pending operation. +   */ +  struct TALER_EXCHANGE_TrackTransactionHandle *tth; + +  /** +   * Interpreter state. +   */ +  struct TALER_TESTING_Interpreter *is; +}; + + +/** + * State for a "track transfer" CMD. + */ +struct TrackTransferState +{ + +  /** +   * Expected amount for the WTID being tracked. +   */ +  const char *expected_total_amount; + +  /** +   * Expected fee for this WTID. +   */ +  const char *expected_wire_fee; + +  /** +   * Expected HTTP response code. +   */ +  unsigned int expected_response_code; + +  /** +   * Reference to any operation that can provide a WTID. +   * Will be the WTID to track. +   */ +  const char *wtid_reference; + +  /** +   * Reference to any operation that can provide wire details. +   * Those wire details will then be matched against the credit +   * bank account of the tracked WTID.  This way we can test that +   * a wire transfer paid back one particular bank account. +   */ +  const char *wire_details_reference; + +  /** +   * Reference to any operation that can provide an amount. +   * This way we can check that the transferred amount matches +   * our expectations. +   */ +  const char *total_amount_reference; + +  /** +   * Index to the WTID to pick, in case @a wtid_reference has +   * many on offer. +   */ +  unsigned int index; + +  /** +   * Handle to a pending "track transfer" operation. +   */ +  struct TALER_EXCHANGE_TrackTransferHandle *tth; + +  /** +   * Interpreter state. +   */ +  struct TALER_TESTING_Interpreter *is; +}; + + +/** + * Checks what is returned by the "track transaction" operation. + * Checks that the HTTP response code is acceptable, and - if the + * right reference is non NULL - that the wire transfer subject + * line matches our expectations. + * + * @param cls closure. + * @param http_status HTTP status code we got. + * @param ec taler-specific error code. + * @param exchange_pub public key of the exchange + * @param json original json reply (may include signatures, those + *        have then been validated already). + * @param wtid wire transfer identifier, NULL if exchange did not + *        execute the transaction yet. + * @param execution_time actual or planned execution time for the + *        wire transfer. + * @param coin_contribution contribution to the total amount of + *        the deposited coin (can be NULL). + */ +static void +deposit_wtid_cb (void *cls, +                 unsigned int http_status, +                 enum TALER_ErrorCode ec, +                 const struct TALER_ExchangePublicKeyP *exchange_pub, +                 const json_t *json, +                 const struct TALER_WireTransferIdentifierRawP *wtid, +                 struct GNUNET_TIME_Absolute execution_time, +                 const struct TALER_Amount *coin_contribution) +{ +  struct TrackTransactionState *tts = cls; +  struct TALER_TESTING_Interpreter *is = tts->is; +  struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; + +  (void) coin_contribution; +  (void) exchange_pub; +  tts->tth = NULL; +  if (tts->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s in %s:%u\n", +                http_status, +                cmd->label, +                __FILE__, +                __LINE__); +    json_dumpf (json, stderr, 0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  switch (http_status) +  { +  case MHD_HTTP_OK: +    tts->wtid = *wtid; +    if (NULL != tts->bank_transfer_reference) +    { +      const struct TALER_TESTING_Command *bank_transfer_cmd; +      const struct TALER_WireTransferIdentifierRawP *wtid_want; + +      /* _this_ wire transfer subject line.  */ +      bank_transfer_cmd = TALER_TESTING_interpreter_lookup_command +                            (is, tts->bank_transfer_reference); +      if (NULL == bank_transfer_cmd) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } + +      if (GNUNET_OK != +          TALER_TESTING_get_trait_wtid +            (bank_transfer_cmd, 0, &wtid_want)) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } + +      /* Compare that expected and gotten subjects match.  */ +      if (0 != GNUNET_memcmp (wtid, +                              wtid_want)) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (tts->is); +        return; +      } +    } +    break; +  case MHD_HTTP_ACCEPTED: +    /* allowed, nothing to check here */ +    break; +  case MHD_HTTP_NOT_FOUND: +    /* allowed, nothing to check here */ +    break; +  default: +    GNUNET_break (0); +    break; +  } +  TALER_TESTING_interpreter_next (tts->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +track_transaction_run (void *cls, +                       const struct TALER_TESTING_Command *cmd, +                       struct TALER_TESTING_Interpreter *is) +{ +  struct TrackTransactionState *tts = cls; +  const struct TALER_TESTING_Command *transaction_cmd; +  const struct TALER_CoinSpendPrivateKeyP *coin_priv; +  struct TALER_CoinSpendPublicKeyP coin_pub; +  const json_t *contract_terms; +  const json_t *wire_details; +  struct GNUNET_HashCode h_wire_details; +  struct GNUNET_HashCode h_contract_terms; +  const struct TALER_MerchantPrivateKeyP *merchant_priv; + +  tts->is = is; +  transaction_cmd +    = TALER_TESTING_interpreter_lookup_command (tts->is, +                                                tts->transaction_reference); +  if (NULL == transaction_cmd) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (tts->is); +    return; +  } + +  if (GNUNET_OK != +      TALER_TESTING_get_trait_coin_priv (transaction_cmd, +                                         tts->coin_index, +                                         &coin_priv)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (tts->is); +    return; +  } + +  GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, +                                      &coin_pub.eddsa_pub); + +  /* Get the strings.. */ +  if (GNUNET_OK != +      TALER_TESTING_get_trait_wire_details (transaction_cmd, +                                            0, +                                            &wire_details)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (tts->is); +    return; +  } + +  if (GNUNET_OK != +      TALER_TESTING_get_trait_contract_terms (transaction_cmd, +                                              0, +                                              &contract_terms)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (tts->is); +    return; +  } + +  if ( (NULL == wire_details) || +       (NULL == contract_terms) ) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (tts->is); +    return; +  } + +  /* Should not fail here, json has been parsed already */ +  GNUNET_assert +    ( (GNUNET_OK == +       TALER_JSON_merchant_wire_signature_hash (wire_details, +                                                &h_wire_details)) && +    (GNUNET_OK == +     TALER_JSON_hash (contract_terms, +                      &h_contract_terms)) ); + +  if (GNUNET_OK != +      TALER_TESTING_get_trait_merchant_priv (transaction_cmd, +                                             0, +                                             &merchant_priv)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (tts->is); +    return; +  } + +  tts->tth = TALER_EXCHANGE_track_transaction (is->exchange, +                                               merchant_priv, +                                               &h_wire_details, +                                               &h_contract_terms, +                                               &coin_pub, +                                               &deposit_wtid_cb, +                                               tts); +  GNUNET_assert (NULL != tts->tth); +} + + +/** + * Cleanup the state from a "track transaction" CMD, and possibly + * cancel a operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +track_transaction_cleanup (void *cls, +                           const struct TALER_TESTING_Command *cmd) +{ +  struct TrackTransactionState *tts = cls; + +  if (NULL != tts->tth) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Command %u (%s) did not complete\n", +                tts->is->ip, +                cmd->label); +    TALER_EXCHANGE_track_transaction_cancel (tts->tth); +    tts->tth = NULL; +  } +  GNUNET_free (tts); +} + + +/** + * Offer internal data from a "track transaction" CMD. + * + * @param cls closure. + * @param[out] ret result (could be anything). + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static int +track_transaction_traits (void *cls, +                          const void **ret, +                          const char *trait, +                          unsigned int index) +{ +  struct TrackTransactionState *tts = cls; +  struct TALER_TESTING_Trait traits[] = { +    TALER_TESTING_make_trait_wtid (0, &tts->wtid), +    TALER_TESTING_trait_end () +  }; + +  return TALER_TESTING_get_trait (traits, +                                  ret, +                                  trait, +                                  index); +} + + +/** + * Create a "track transaction" command. + * + * @param label the command label. + * @param transaction_reference reference to a deposit operation, + *        will be used to get the input data for the track. + * @param coin_index index of the coin involved in the transaction. + * @param expected_response_code expected HTTP response code. + * @param bank_transfer_reference reference to a command that + *        can offer a WTID so as to check that against what WTID + *        the tracked operation has.  Set as NULL if not needed. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_track_transaction (const char *label, +                                     const char *transaction_reference, +                                     unsigned int coin_index, +                                     unsigned int expected_response_code, +                                     const char *bank_transfer_reference) +{ +  struct TrackTransactionState *tts; + +  tts = GNUNET_new (struct TrackTransactionState); +  tts->transaction_reference = transaction_reference; +  tts->expected_response_code = expected_response_code; +  tts->bank_transfer_reference = bank_transfer_reference; +  tts->coin_index = coin_index; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = tts, +      .label = label, +      .run = &track_transaction_run, +      .cleanup = &track_transaction_cleanup, +      .traits = &track_transaction_traits +    }; + +    return cmd; +  } +} + + +/** + * Cleanup the state for a "track transfer" CMD, and possibly + * cancel a pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +track_transfer_cleanup (void *cls, +                        const struct TALER_TESTING_Command *cmd) +{ + +  struct TrackTransferState *tts = cls; + +  if (NULL != tts->tth) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Command %u (%s) did not complete\n", +                tts->is->ip, +                cmd->label); +    TALER_EXCHANGE_track_transfer_cancel (tts->tth); +    tts->tth = NULL; +  } +  GNUNET_free (tts); +} + + +/** + * Check whether the HTTP response code from a "track transfer" + * operation is acceptable, and all other values like total amount, + * wire fees and hashed wire details as well. + * + * @param cls closure. + * @param http_status HTTP status code we got. + * @param ec taler-specific error code. + * @param exchange_pub public key the exchange used for signing + *        the response. + * @param json original json reply (may include signatures, those + *        have then been validated already). + * @param h_wire hash of the wire transfer address the transfer + *        went to, or NULL on error. + * @param execution_time time when the exchange claims to have + *        performed the wire transfer. + * @param total_amount total amount of the wire transfer, or NULL + *        if the exchange could not provide any @a wtid (set only + *        if @a http_status is "200 OK"). + * @param wire_fee wire fee that was charged by the exchange. + * @param details_length length of the @a details array. + * @param details array with details about the combined + *        transactions. + */ +static void +track_transfer_cb (void *cls, +                   unsigned int http_status, +                   enum TALER_ErrorCode ec, +                   const struct TALER_ExchangePublicKeyP *exchange_pub, +                   const json_t *json, +                   const struct GNUNET_HashCode *h_wire, +                   struct GNUNET_TIME_Absolute execution_time, +                   const struct TALER_Amount *total_amount, +                   const struct TALER_Amount *wire_fee, +                   unsigned int details_length, +                   const struct TALER_TrackTransferDetails *details) +{ +  struct TrackTransferState *tts = cls; +  struct TALER_TESTING_Interpreter *is = tts->is; +  struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; +  struct TALER_Amount expected_amount; + +  (void) exchange_pub; +  tts->tth = NULL; +  if (tts->expected_response_code != http_status) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u to command %s in %s:%u\n", +                http_status, +                cmd->label, +                __FILE__, +                __LINE__); +    json_dumpf (json, stderr, 0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  switch (http_status) +  { +  case MHD_HTTP_OK: +    if (NULL == tts->expected_total_amount) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (is); +      return; +    } +    if (NULL == tts->expected_wire_fee) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (is); +      return; +    } + +    if (GNUNET_OK != +        TALER_string_to_amount (tts->expected_total_amount, +                                &expected_amount)) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (is); +      return; +    } +    if (0 != TALER_amount_cmp (total_amount, +                               &expected_amount)) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Total amount missmatch to command %s - " +                  "%s vs %s\n", +                  cmd->label, +                  TALER_amount_to_string (total_amount), +                  TALER_amount_to_string (&expected_amount)); +      json_dumpf (json, stderr, 0); +      fprintf (stderr, "\n"); +      TALER_TESTING_interpreter_fail (is); +      return; +    } + +    if (GNUNET_OK != +        TALER_string_to_amount (tts->expected_wire_fee, +                                &expected_amount)) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (is); +      return; +    } + +    if (0 != TALER_amount_cmp (wire_fee, +                               &expected_amount)) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "Wire fee missmatch to command %s\n", +                  cmd->label); +      json_dumpf (json, 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 json_t *wire_details; +      struct GNUNET_HashCode h_wire_details; + +      if (NULL == (wire_details_cmd +                     = TALER_TESTING_interpreter_lookup_command +                         (is, tts->wire_details_reference))) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } + +      if (GNUNET_OK != +          TALER_TESTING_get_trait_wire_details (wire_details_cmd, +                                                0, +                                                &wire_details)) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } + +      GNUNET_assert +        (GNUNET_OK == +        TALER_JSON_merchant_wire_signature_hash (wire_details, +                                                 &h_wire_details)); + +      if (0 != GNUNET_memcmp (&h_wire_details, +                              h_wire)) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Wire hash missmath to command %s\n", +                    cmd->label); +        json_dumpf (json, 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; + +      if (NULL == (total_amount_cmd +                     = TALER_TESTING_interpreter_lookup_command +                         (is, tts->total_amount_reference))) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } + +      if (GNUNET_OK != +          TALER_TESTING_get_trait_amount_obj (total_amount_cmd, +                                              0, +                                              &total_amount_from_reference)) +      { +        GNUNET_break (0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } + +      if (0 != TALER_amount_cmp (total_amount, +                                 total_amount_from_reference)) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                    "Amount missmath to command %s\n", +                    cmd->label); +        json_dumpf (json, stderr, 0); +        TALER_TESTING_interpreter_fail (is); +        return; +      } +    } +  } +  TALER_TESTING_interpreter_next (is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command under execution. + * @param is the interpreter state. + */ +static void +track_transfer_run (void *cls, +                    const struct TALER_TESTING_Command *cmd, +                    struct TALER_TESTING_Interpreter *is) +{ +  /* looking for a wtid to track .. */ +  struct TrackTransferState *tts = cls; +  struct TALER_WireTransferIdentifierRawP wtid; +  const struct TALER_WireTransferIdentifierRawP *wtid_ptr; + +  /* 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) +  { +    const struct TALER_TESTING_Command *wtid_cmd; + +    wtid_cmd = TALER_TESTING_interpreter_lookup_command +                 (tts->is, tts->wtid_reference); + +    if (NULL == wtid_cmd) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (tts->is); +      return; +    } + +    if (GNUNET_OK != TALER_TESTING_get_trait_wtid +          (wtid_cmd, tts->index, &wtid_ptr)) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (tts->is); +      return; +    } +    GNUNET_assert (NULL != wtid_ptr); +  } +  tts->tth = TALER_EXCHANGE_track_transfer (is->exchange, +                                            wtid_ptr, +                                            &track_transfer_cb, +                                            tts); +  GNUNET_assert (NULL != tts->tth); +} + + +/** + * Make a "track transfer" CMD where no "expected"-arguments, + * except the HTTP response code, are given.  The best use case + * is when what matters to check is the HTTP response code, e.g. + * when a bogus WTID was passed. + * + * @param label the command label + * @param wtid_reference reference to any command which can provide + *        a wtid.  If NULL is given, then a all zeroed WTID is + *        used that will at 99.9999% probability NOT match any + *        existing WTID known to the exchange. + * @param index index number of the WTID to track, in case there + *        are multiple on offer. + * @param expected_response_code expected HTTP response code. + * @return the command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_track_transfer_empty (const char *label, +                                        const char *wtid_reference, +                                        unsigned int index, +                                        unsigned int expected_response_code) +{ +  struct TrackTransferState *tts; + +  tts = GNUNET_new (struct TrackTransferState); +  tts->wtid_reference = wtid_reference; +  tts->index = index; +  tts->expected_response_code = expected_response_code; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = tts, +      .label = label, +      .run = &track_transfer_run, +      .cleanup = &track_transfer_cleanup +    }; + +    return cmd; +  } +} + + +/** + * Make a "track transfer" command, specifying which amount and + * wire fee are expected. + * + * @param label the command label. + * @param wtid_reference reference to any command which can provide + *        a wtid.  Will be the one tracked. + * @param index in case there are multiple WTID offered, this + *        parameter selects a particular one. + * @param expected_response_code expected HTTP response code. + * @param expected_total_amount how much money we expect being moved + *        with this wire-transfer. + * @param expected_wire_fee expected wire fee. + * @return the command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_track_transfer (const char *label, +                                  const char *wtid_reference, +                                  unsigned int index, +                                  unsigned int expected_response_code, +                                  const char *expected_total_amount, +                                  const char *expected_wire_fee) +{ +  struct TrackTransferState *tts; + +  tts = GNUNET_new (struct TrackTransferState); +  tts->wtid_reference = wtid_reference; +  tts->index = index; +  tts->expected_response_code = expected_response_code; +  tts->expected_total_amount = expected_total_amount; +  tts->expected_wire_fee = expected_wire_fee; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = tts, +      .label = label, +      .run = &track_transfer_run, +      .cleanup = &track_transfer_cleanup +    }; + +    return cmd; +  } +} + + +/* end of testing_api_cmd_track.c */ diff --git a/src/testing/testing_api_cmd_wait.c b/src/testing/testing_api_cmd_wait.c new file mode 100644 index 00000000..5558f7b9 --- /dev/null +++ b/src/testing/testing_api_cmd_wait.c @@ -0,0 +1,133 @@ +/* +  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_wait.c + * @brief command(s) to wait on some process + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_testing_lib.h" + + +/** + * Cleanup the state from a "wait service" CMD. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +wait_service_cleanup (void *cls, +                      const struct TALER_TESTING_Command *cmd) +{ +  (void) cls; +  (void) cmd; +  /* nothing to clean.  */ +  return; +} + + +/** + * No traits to offer, just provide a stub to be called when + * some CMDs iterates through the list of all the commands. + * + * @param cls closure. + * @param[out] ret result. + * @param trait name of the trait. + * @param index index number of the trait to return. + * @return #GNUNET_OK on success. + */ +static int +wait_service_traits (void *cls, +                     const void **ret, +                     const char *trait, +                     unsigned int index) +{ +  (void) cls; +  (void) ret; +  (void) trait; +  (void) index; +  return GNUNET_NO; +} + + +/** + * Run a "wait service" CMD. + * + * @param cls closure. + * @param cmd the command being run. + * @param is the interpreter state. + */ +static void +wait_service_run (void *cls, +                  const struct TALER_TESTING_Command *cmd, +                  struct TALER_TESTING_Interpreter *is) +{ +  unsigned int iter = 0; +  const char *url = cmd->cls; +  char *wget_cmd; + +  GNUNET_asprintf (&wget_cmd, +                   "wget -q -t 1 -T 1 %s -o /dev/null -O /dev/null", +                   url); +  do +  { +    fprintf (stderr, "."); + +    if (10 == iter++) +    { +      TALER_LOG_ERROR ("Could not reach the proxied service\n"); +      TALER_TESTING_interpreter_fail (is); +      GNUNET_free (wget_cmd); +      return; +    } +  } +  while (0 != system (wget_cmd)); + +  GNUNET_free (wget_cmd); +  TALER_TESTING_interpreter_next (is); +} + + +/** + * This CMD simply tries to connect via HTTP to the + * service addressed by @a url.  It attemps 10 times + * before giving up and make the test fail. + * + * @param label label for the command. + * @param url complete URL to connect to. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_wait_service (const char *label, +                                const char *url) +{ +  struct TALER_TESTING_Command cmd = { +    .label = label, +    .run = wait_service_run, +    .cleanup = wait_service_cleanup, +    .traits = wait_service_traits, +    .cls = (void *) url +  }; + +  return cmd; +} + + +/* end of testing_api_cmd_sleep.c  */ diff --git a/src/testing/testing_api_cmd_wire.c b/src/testing/testing_api_cmd_wire.c new file mode 100644 index 00000000..5d1f2454 --- /dev/null +++ b/src/testing/testing_api_cmd_wire.c @@ -0,0 +1,234 @@ +/* +  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_wire.c + * @brief command for testing /wire. + * @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 "wire" CMD. + */ +struct WireState +{ + +  /** +   * Handle to the /wire operation. +   */ +  struct TALER_EXCHANGE_WireHandle *wh; + +  /** +   * Which wire-method we expect is offered by the exchange. +   */ +  const char *expected_method; + +  /** +   * Flag indicating if the expected method is actually +   * offered. +   */ +  unsigned int method_found; + +  /** +   * Fee we expect is charged for this wire-transfer method. +   */ +  const char *expected_fee; + +  /** +   * Expected HTTP response code. +   */ +  unsigned int expected_response_code; + +  /** +   * Interpreter state. +   */ +  struct TALER_TESTING_Interpreter *is; +}; + + +/** + * Check whether the HTTP response code is acceptable, that + * the expected wire method is offered by the exchange, and + * that the wire fee is acceptable too. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param accounts_len length of the @a accounts array. + * @param accounts list of wire accounts of the exchange, + *        NULL on error. + */ +static void +wire_cb (void *cls, +         unsigned int http_status, +         enum TALER_ErrorCode ec, +         unsigned int accounts_len, +         const struct TALER_EXCHANGE_WireAccount *accounts) +{ +  struct WireState *ws = cls; +  struct TALER_TESTING_Command *cmd = \ +    &ws->is->commands[ws->is->ip]; +  struct TALER_Amount expected_fee; + +  TALER_LOG_DEBUG ("Checking parsed /wire response\n"); +  ws->wh = NULL; +  if (ws->expected_response_code != http_status) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (ws->is); +    return; +  } + +  if (MHD_HTTP_OK == http_status) +  { +    for (unsigned int i = 0; i<accounts_len; i++) +    { +      char *method; + +      method = TALER_payto_get_method (accounts[i].url); +      if (0 == strcmp (ws->expected_method, +                       method)) +      { +        ws->method_found = GNUNET_OK; +        if (NULL != ws->expected_fee) +        { +          GNUNET_assert +            (GNUNET_OK == +            TALER_string_to_amount (ws->expected_fee, +                                    &expected_fee)); +          const struct TALER_EXCHANGE_WireAggregateFees *waf; +          for (waf = accounts[i].fees; +               NULL != waf; +               waf = waf->next) +          { +            if (0 != TALER_amount_cmp (&waf->wire_fee, +                                       &expected_fee)) +            { +              GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                          "Wire fee missmatch to command %s\n", +                          cmd->label); +              TALER_TESTING_interpreter_fail (ws->is); +              GNUNET_free (method); +              return; +            } +          } +        } +      } +      TALER_LOG_DEBUG ("Freeing method '%s'\n", +                       method); +      GNUNET_free (method); +    } +    if (GNUNET_OK != ws->method_found) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                  "/wire does not offer method '%s'\n", +                  ws->expected_method); +      TALER_TESTING_interpreter_fail (ws->is); +      return; +    } +  } + +  TALER_TESTING_interpreter_next (ws->is); +} + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the command to execute. + * @param is the interpreter state. + */ +static void +wire_run (void *cls, +          const struct TALER_TESTING_Command *cmd, +          struct TALER_TESTING_Interpreter *is) +{ +  struct WireState *ws = cls; +  ws->is = is; +  ws->wh = TALER_EXCHANGE_wire (is->exchange, +                                &wire_cb, +                                ws); +} + + +/** + * Cleanup the state of a "wire" CMD, and possibly cancel a + * pending operation thereof. + * + * @param cls closure. + * @param cmd the command which is being cleaned up. + */ +static void +wire_cleanup (void *cls, +              const struct TALER_TESTING_Command *cmd) +{ +  struct WireState *ws = cls; + +  if (NULL != ws->wh) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Command %u (%s) did not complete\n", +                ws->is->ip, +                cmd->label); +    TALER_EXCHANGE_wire_cancel (ws->wh); +    ws->wh = NULL; +  } +  GNUNET_free (ws); +} + + +/** + * 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, +                        const char *expected_fee, +                        unsigned int expected_response_code) +{ +  struct WireState *ws; + +  ws = GNUNET_new (struct WireState); +  ws->expected_method = expected_method; +  ws->expected_fee = expected_fee; +  ws->expected_response_code = expected_response_code; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ws, +      .label = label, +      .run = &wire_run, +      .cleanup = &wire_cleanup +    }; + +    return cmd; +  } +} diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c new file mode 100644 index 00000000..b6242055 --- /dev/null +++ b/src/testing/testing_api_cmd_withdraw.c @@ -0,0 +1,534 @@ +/* +  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_cmd_withdraw.c + * @brief main interpreter loop for testcases + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <microhttpd.h> +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" +#include "backoff.h" + + +/** + * State for a "withdraw" CMD. + */ +struct WithdrawState +{ + +  /** +   * Which reserve should we withdraw from? +   */ +  const char *reserve_reference; + +  /** +   * 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; + +  /** +   * Exchange base URL.  Only used as offered trait. +   */ +  char *exchange_url; + +  /** +   * Interpreter state (during command). +   */ +  struct TALER_TESTING_Interpreter *is; + +  /** +   * 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_PlanchetSecretsP ps; + +  /** +   * Withdraw handle (while operation is running). +   */ +  struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh; + +  /** +   * 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 to the request. +   */ +  unsigned int expected_response_code; + +  /** +   * Was this command modified via +   * #TALER_TESTING_cmd_withdraw_with_retry to +   * enable retries? +   */ +  int do_retry; +}; + + +/** + * Run the command. + * + * @param cls closure. + * @param cmd the commaind being run. + * @param is interpreter state. + */ +static void +withdraw_run (void *cls, +              const struct TALER_TESTING_Command *cmd, +              struct TALER_TESTING_Interpreter *is); + + +/** + * Task scheduled to re-try #withdraw_run. + * + * @param cls a `struct WithdrawState` + */ +static void +do_retry (void *cls) +{ +  struct WithdrawState *ws = cls; + +  ws->retry_task = NULL; +  withdraw_run (ws, +                NULL, +                ws->is); +} + + +/** + * "reserve withdraw" operation callback; checks that the + * response code is expected and store the exchange signature + * in the state. + * + * @param cls closure. + * @param http_status HTTP response code. + * @param ec taler-specific error code. + * @param sig signature over the coin, NULL on error. + * @param full_response raw response. + */ +static void +reserve_withdraw_cb (void *cls, +                     unsigned int http_status, +                     enum TALER_ErrorCode ec, +                     const struct TALER_DenominationSignature *sig, +                     const json_t *full_response) +{ +  struct WithdrawState *ws = cls; +  struct TALER_TESTING_Interpreter *is = ws->is; + +  ws->wsh = NULL; +  if (ws->expected_response_code != http_status) +  { +    if (GNUNET_YES == ws->do_retry) +    { +      if ( (0 == http_status) || +           (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) || +           (TALER_EC_WITHDRAW_INSUFFICIENT_FUNDS == ec) || +           (TALER_EC_WITHDRAW_RESERVE_UNKNOWN == ec) || +           (MHD_HTTP_INTERNAL_SERVER_ERROR == http_status) ) +      { +        GNUNET_log (GNUNET_ERROR_TYPE_INFO, +                    "Retrying withdraw failed with %u/%d\n", +                    http_status, +                    (int) ec); +        /* on DB conflicts, do not use backoff */ +        if (TALER_EC_DB_COMMIT_FAILED_ON_RETRY == ec) +          ws->backoff = GNUNET_TIME_UNIT_ZERO; +        else +          ws->backoff = EXCHANGE_LIB_BACKOFF (ws->backoff); +        ws->retry_task = GNUNET_SCHEDULER_add_delayed (ws->backoff, +                                                       &do_retry, +                                                       ws); +        return; +      } +    } +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected response code %u/%d to command %s in %s:%u\n", +                http_status, +                (int) ec, +                TALER_TESTING_interpreter_get_current_label (is), +                __FILE__, +                __LINE__); +    json_dumpf (full_response, +                stderr, +                0); +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  switch (http_status) +  { +  case MHD_HTTP_OK: +    if (NULL == sig) +    { +      GNUNET_break (0); +      TALER_TESTING_interpreter_fail (is); +      return; +    } +    ws->sig.rsa_signature +      = GNUNET_CRYPTO_rsa_signature_dup (sig->rsa_signature); +    break; +  case MHD_HTTP_FORBIDDEN: +    /* nothing to check */ +    break; +  case MHD_HTTP_CONFLICT: +    /* nothing to check */ +    break; +  case MHD_HTTP_NOT_FOUND: +    /* nothing to check */ +    break; +  default: +    /* Unsupported status code (by test harness) */ +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Withdraw test command does not support status code %u\n", +                http_status); +    GNUNET_break (0); +    break; +  } +  TALER_TESTING_interpreter_next (is); +} + + +/** + * Run the command. + */ +static void +withdraw_run (void *cls, +              const struct TALER_TESTING_Command *cmd, +              struct TALER_TESTING_Interpreter *is) +{ +  struct WithdrawState *ws = cls; +  const struct TALER_ReservePrivateKeyP *rp; +  const struct TALER_TESTING_Command *create_reserve; +  const struct TALER_EXCHANGE_DenomPublicKey *dpk; + +  (void) cmd; +  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, +                                            0, +                                            &rp)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } +  TALER_planchet_setup_random (&ws->ps); +  ws->is = is; + +  dpk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange), +                               &ws->amount); +  if (NULL == dpk) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to determine denomination key at %s\n", +                (NULL != cmd) ? cmd->label : "<retried command>"); +    GNUNET_assert (0); +  } +  else +  { +    /* We copy the denomination key, as re-querying /keys +     * would free the old one. */ +    ws->pk = TALER_EXCHANGE_copy_denomination_key (dpk); +  } + +  ws->wsh = TALER_EXCHANGE_reserve_withdraw (is->exchange, +                                             ws->pk, +                                             rp, +                                             &ws->ps, +                                             &reserve_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 +withdraw_cleanup (void *cls, +                  const struct TALER_TESTING_Command *cmd) +{ +  struct WithdrawState *ws = cls; + +  if (NULL != ws->wsh) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Command %s did not complete\n", +                cmd->label); +    TALER_EXCHANGE_reserve_withdraw_cancel (ws->wsh); +    ws->wsh = NULL; +  } +  if (NULL != ws->retry_task) +  { +    GNUNET_SCHEDULER_cancel (ws->retry_task); +    ws->retry_task = NULL; +  } +  if (NULL != ws->sig.rsa_signature) +  { +    GNUNET_CRYPTO_rsa_signature_free (ws->sig.rsa_signature); +    ws->sig.rsa_signature = NULL; +  } +  if (NULL != ws->pk) +  { +    TALER_EXCHANGE_destroy_denomination_key (ws->pk); +    ws->pk = NULL; +  } +  GNUNET_free_non_null (ws->exchange_url); +  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 int +withdraw_traits (void *cls, +                 const void **ret, +                 const char *trait, +                 unsigned int index) +{ +  struct WithdrawState *ws = cls; +  const struct TALER_TESTING_Command *reserve_cmd; +  const struct TALER_ReservePrivateKeyP *reserve_priv; +  const struct TALER_ReservePublicKeyP *reserve_pub; + +  /* We offer the reserve key where these coins were withdrawn +   * from. */ +  reserve_cmd = TALER_TESTING_interpreter_lookup_command +                  (ws->is, ws->reserve_reference); + +  if (NULL == reserve_cmd) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (ws->is); +    return GNUNET_SYSERR; +  } + +  if (GNUNET_OK != +      TALER_TESTING_get_trait_reserve_priv (reserve_cmd, +                                            0, +                                            &reserve_priv)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (ws->is); +    return GNUNET_SYSERR; +  } +  if (GNUNET_OK != +      TALER_TESTING_get_trait_reserve_pub (reserve_cmd, +                                           0, +                                           &reserve_pub)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (ws->is); +    return GNUNET_SYSERR; +  } +  if (NULL == ws->exchange_url) +    ws->exchange_url +      = GNUNET_strdup (TALER_EXCHANGE_get_base_url (ws->is->exchange)); +  { +    struct TALER_TESTING_Trait traits[] = { +      TALER_TESTING_make_trait_coin_priv (0 /* only one coin */, +                                          &ws->ps.coin_priv), +      TALER_TESTING_make_trait_blinding_key (0 /* only one coin */, +                                             &ws->ps.blinding_key), +      TALER_TESTING_make_trait_denom_pub (0 /* only one coin */, +                                          ws->pk), +      TALER_TESTING_make_trait_denom_sig (0 /* only one coin */, +                                          &ws->sig), +      TALER_TESTING_make_trait_reserve_priv (0, +                                             reserve_priv), +      TALER_TESTING_make_trait_reserve_pub (0, +                                            reserve_pub), +      TALER_TESTING_make_trait_amount_obj (0, +                                           &ws->amount), +      TALER_TESTING_make_trait_url (TALER_TESTING_UT_EXCHANGE_BASE_URL, +                                    ws->exchange_url), +      TALER_TESTING_trait_end () +    }; + +    return TALER_TESTING_get_trait (traits, +                                    ret, +                                    trait, +                                    index); +  } +} + + +/** + * Create a withdraw command, letting the caller specify + * the desired amount as string. + * + * @param label command label. + * @param reserve_reference command providing us with a reserve to withdraw from + * @param amount how much we withdraw. + * @param expected_response_code which HTTP response code + *        we expect from the exchange. + * @return the withdraw command to be executed by the interpreter. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_withdraw_amount (const char *label, +                                   const char *reserve_reference, +                                   const char *amount, +                                   unsigned int expected_response_code) +{ +  struct WithdrawState *ws; + +  ws = GNUNET_new (struct WithdrawState); +  ws->reserve_reference = reserve_reference; +  if (GNUNET_OK != +      TALER_string_to_amount (amount, +                              &ws->amount)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to parse amount `%s' at %s\n", +                amount, +                label); +    GNUNET_assert (0); +  } +  ws->expected_response_code = expected_response_code; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ws, +      .label = label, +      .run = &withdraw_run, +      .cleanup = &withdraw_cleanup, +      .traits = &withdraw_traits +    }; + +    return cmd; +  } +} + + +/** + * 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, +                                         const char *reserve_reference, +                                         const struct +                                         TALER_EXCHANGE_DenomPublicKey *dk, +                                         unsigned int expected_response_code) +{ +  struct WithdrawState *ws; + +  if (NULL == dk) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Denomination key not specified at %s\n", +                label); +    GNUNET_assert (0); +  } +  ws = GNUNET_new (struct WithdrawState); +  ws->reserve_reference = reserve_reference; +  ws->pk = TALER_EXCHANGE_copy_denomination_key (dk); +  ws->expected_response_code = expected_response_code; +  { +    struct TALER_TESTING_Command cmd = { +      .cls = ws, +      .label = label, +      .run = &withdraw_run, +      .cleanup = &withdraw_cleanup, +      .traits = &withdraw_traits +    }; + +    return cmd; +  } +} + + +/** + * 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) +{ +  struct WithdrawState *ws; + +  GNUNET_assert (&withdraw_run == cmd.run); +  ws = cmd.cls; +  ws->do_retry = GNUNET_YES; +  return cmd; +} + + +/* end of testing_api_cmd_withdraw.c */ diff --git a/src/testing/testing_api_helpers_auditor.c b/src/testing/testing_api_helpers_auditor.c new file mode 100644 index 00000000..ccfa5e24 --- /dev/null +++ b/src/testing/testing_api_helpers_auditor.c @@ -0,0 +1,229 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as +  published by the Free Software Foundation; either version 3, or +  (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_helpers_auditor.c + * @brief helper functions + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_testing_lib.h" +#include "taler_auditor_service.h" + + +/** + * Closure for #cleanup_auditor. + */ +struct CleanupContext +{ +  /** +   * Where we find the state to clean up. +   */ +  struct TALER_TESTING_Interpreter *is; + +  /** +   * Next cleanup routine to call, NULL for none. +   */ +  GNUNET_SCHEDULER_TaskCallback fcb; + +  /** +   * Closure for @e fcb +   */ +  void *fcb_cls; +}; + + +/** + * Function to clean up the auditor connection. + * + * @param cls a `struct CleanupContext` + */ +static void +cleanup_auditor (void *cls) +{ +  struct CleanupContext *cc = cls; +  struct TALER_TESTING_Interpreter *is = cc->is; + +  TALER_AUDITOR_disconnect (is->auditor); +  is->auditor = NULL; +  if (NULL != cc->fcb) +    cc->fcb (cc->fcb_cls); +  GNUNET_free (cc); +} + + +/** + * Closure for #auditor_main_wrapper() + */ +struct MainWrapperContext +{ +  /** +   * Main function to launch. +   */ +  TALER_TESTING_Main main_cb; + +  /** +   * Closure for @e main_cb. +   */ +  void *main_cb_cls; + +  /** +   * Configuration we use. +   */ +  const struct GNUNET_CONFIGURATION_Handle *cfg; + +  /** +   * Name of the configuration file. +   */ +  const char *config_filename; + +}; + + +/** + * Function called with information about the auditor. + * + * @param cls closure + * @param vi basic information about the auditor + * @param compat protocol compatibility information + */ +static void +auditor_version_cb (void *cls, +                    const struct TALER_AUDITOR_VersionInformation *vi, +                    enum TALER_AUDITOR_VersionCompatibility compat) +{ +  struct TALER_TESTING_Interpreter *is = cls; + +  if (TALER_AUDITOR_VC_MATCH != compat) +  { +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  is->auditor_working = GNUNET_YES; +} + + +/** + * Setup the @a is 'auditor' member before running the main test loop. + * + * @param cls must be a `struct MainWrapperContext *` + * @param[in,out] is interpreter state to setup + */ +static void +auditor_main_wrapper (void *cls, +                      struct TALER_TESTING_Interpreter *is) +{ +  struct MainWrapperContext *mwc = cls; +  struct CleanupContext *cc; +  char *auditor_base_url; + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_string (mwc->cfg, +                                             "auditor", +                                             "BASE_URL", +                                             &auditor_base_url)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                               "auditor", +                               "BASE_URL"); +    return; +  } + +  is->auditor = TALER_AUDITOR_connect (is->ctx, +                                       auditor_base_url, +                                       &auditor_version_cb, +                                       is); +  GNUNET_free (auditor_base_url); + +  if (NULL == is->auditor) +  { +    GNUNET_break (0); +    return; +  } + +  cc = GNUNET_new (struct CleanupContext); +  cc->is = is; +  cc->fcb = is->final_cleanup_cb; +  cc->fcb_cls = is->final_cleanup_cb_cls; +  is->final_cleanup_cb = cleanup_auditor; +  is->final_cleanup_cb_cls = cc; +  mwc->main_cb (mwc->main_cb_cls, +                is); +} + + +/** + * Install signal handlers plus schedules the main wrapper + * around the "run" method. + * + * @param cls our `struct MainWrapperContext` + * @param cfg configuration we use + * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise. + *         non-GNUNET_OK codes are #GNUNET_SYSERR most of the + *         times. + */ +static int +setup_with_cfg (void *cls, +                const struct GNUNET_CONFIGURATION_Handle *cfg) +{ +  struct MainWrapperContext *mwc = cls; +  struct TALER_TESTING_SetupContext setup_ctx = { +    .config_filename = mwc->config_filename, +    .main_cb = &auditor_main_wrapper, +    .main_cb_cls = mwc +  }; + +  mwc->cfg = cfg; +  return TALER_TESTING_setup_with_auditor_and_exchange_cfg (&setup_ctx, +                                                            cfg); +} + + +/** + * Install signal handlers plus schedules the main wrapper + * around the "run" method. + * + * @param main_cb the "run" method which contains all the + *        commands. + * @param main_cb_cls a closure for "run", typically NULL. + * @param config_filename configuration filename. + * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise. + *         non-GNUNET_OK codes are #GNUNET_SYSERR most of the + *         times. + */ +int +TALER_TESTING_auditor_setup (TALER_TESTING_Main main_cb, +                             void *main_cb_cls, +                             const char *config_filename) +{ +  struct MainWrapperContext mwc = { +    .main_cb = main_cb, +    .main_cb_cls = main_cb_cls, +    .config_filename = config_filename +  }; + +  return GNUNET_CONFIGURATION_parse_and_run (config_filename, +                                             &setup_with_cfg, +                                             &mwc); +} + + +/* end of testing_auditor_api_helpers.c */ diff --git a/src/testing/testing_api_helpers_bank.c b/src/testing/testing_api_helpers_bank.c new file mode 100644 index 00000000..dbe89e63 --- /dev/null +++ b/src/testing/testing_api_helpers_bank.c @@ -0,0 +1,494 @@ +/* +  This file is part of TALER +  Copyright (C) 2018-2020 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as +  published by the Free Software Foundation; either version 3, or +  (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_helpers_bank.c + * @brief convenience functions for bank tests. + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_testing_lib.h" +#include "taler_fakebank_lib.h" + +#define BANK_FAIL() \ +  do {GNUNET_break (0); return NULL; } while (0) + + +/** + * Runs the Fakebank by guessing / extracting the portnumber + * from the base URL. + * + * @param bank_url bank's base URL. + * @return the fakebank process handle, or NULL if any + *         error occurs. + */ +struct TALER_FAKEBANK_Handle * +TALER_TESTING_run_fakebank (const char *bank_url) +{ +  const char *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); +  if (NULL == fakebankd) +  { +    GNUNET_break (0); +    return NULL; +  } +  return fakebankd; +} + + +/** + * Look for substring in a programs' name. + * + * @param prog program's name to look into + * @param marker chunk to find in @a prog + * @return #GNUNET_YES if @a marker is present, otherwise #GNUNET_NO + */ +int +TALER_TESTING_has_in_name (const char *prog, +                           const char *marker) +{ +  size_t name_pos; +  size_t pos; + +  if (! prog || ! marker) +    return GNUNET_NO; + +  pos = 0; +  name_pos = 0; +  while (prog[pos]) +  { +    if ('/' == prog[pos]) +      name_pos = pos + 1; +    pos++; +  } +  if (name_pos == pos) +    return GNUNET_YES; +  return (NULL != strstr (prog + name_pos, marker)); +} + + +/** + * Start the (Python) bank process.  Assume the port + * is available and the database is clean.  Use the "prepare + * bank" function to do such tasks. + * + * @param config_filename configuration filename. + * @param bank_url base URL of the bank, used by `wget' to check + *        that the bank was started right. + * @return the process, or NULL if the process could not + *         be started. + */ +struct GNUNET_OS_Process * +TALER_TESTING_run_bank (const char *config_filename, +                        const char *bank_url) +{ +  struct GNUNET_OS_Process *bank_proc; +  unsigned int iter; +  char *wget_cmd; +  char *database; +  char *serve_cfg; +  char *serve_arg; +  struct GNUNET_CONFIGURATION_Handle *cfg; + +  cfg = GNUNET_CONFIGURATION_create (); +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_load (cfg, +                                 config_filename)) +  { +    GNUNET_break (0); +    GNUNET_CONFIGURATION_destroy (cfg); +    exit (77); +  } + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_string (cfg, +                                             "bank", +                                             "database", +                                             &database)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, +                               "bank", +                               "database"); +    GNUNET_break (0); +    GNUNET_CONFIGURATION_destroy (cfg); +    exit (77); +  } + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_string (cfg, +                                             "bank", +                                             "serve", +                                             &serve_cfg)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, +                               "bank", +                               "serve"); +    GNUNET_break (0); +    GNUNET_CONFIGURATION_destroy (cfg); +    GNUNET_free (database); +    exit (77); +  } +  GNUNET_CONFIGURATION_destroy (cfg); + +  serve_arg = "serve-http"; +  if (0 != strcmp ("http", serve_cfg)) +    serve_arg = "serve-uwsgi"; +  GNUNET_free (serve_cfg); +  bank_proc = GNUNET_OS_start_process +                (GNUNET_NO, +                GNUNET_OS_INHERIT_STD_ALL, +                NULL, NULL, NULL, +                "taler-bank-manage-testing", +                "taler-bank-manage-testing", +                config_filename, +                database, +                serve_arg, NULL); +  GNUNET_free (database); +  if (NULL == bank_proc) +  { +    BANK_FAIL (); +  } + +  GNUNET_asprintf (&wget_cmd, +                   "wget -q -t 2 -T 1 %s -o /dev/null -O /dev/null", +                   bank_url); + +  /* give child time to start and bind against the socket */ +  fprintf (stderr, +           "Waiting for `taler-bank-manage' to be ready (via %s)\n", wget_cmd); +  iter = 0; +  do +  { +    if (10 == iter) +    { +      fprintf ( +        stderr, +        "Failed to launch `taler-bank-manage' (or `wget')\n"); +      GNUNET_OS_process_kill (bank_proc, +                              SIGTERM); +      GNUNET_OS_process_wait (bank_proc); +      GNUNET_OS_process_destroy (bank_proc); +      GNUNET_free (wget_cmd); +      BANK_FAIL (); +    } +    fprintf (stderr, "."); +    sleep (1); +    iter++; +  } +  while (0 != system (wget_cmd)); +  GNUNET_free (wget_cmd); +  fprintf (stderr, "\n"); + +  return bank_proc; + +} + + +/** + * Prepare the bank execution.  Check if the port is available + * and reset database. + * + * @param config_filename configuration file name. + * @param config_section section of the configuration with the exchange's account + * @param[out] bc set to the bank's configuration data + * @return the base url, or NULL upon errors.  Must be freed + *         by the caller. + */ +int +TALER_TESTING_prepare_bank (const char *config_filename, +                            const char *config_section, +                            struct TALER_TESTING_BankConfiguration *bc) +{ +  struct GNUNET_CONFIGURATION_Handle *cfg; +  unsigned long long port; +  struct GNUNET_OS_Process *dbreset_proc; +  enum GNUNET_OS_ProcessStatusType type; +  unsigned long code; +  char *database; +  char *exchange_payto_uri; + +  cfg = GNUNET_CONFIGURATION_create (); + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_load (cfg, config_filename)) +  { +    GNUNET_CONFIGURATION_destroy (cfg); +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_string (cfg, +                                             "bank", +                                             "DATABASE", +                                             &database)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                               "bank", +                               "DATABASE"); +    GNUNET_CONFIGURATION_destroy (cfg); +    GNUNET_break (0); +    return GNUNET_SYSERR; +  } + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_string (cfg, +                                             config_section, +                                             "URL", /* FIXME: config should be renamed to payto_uri, it's not an url even! */ +                                             &exchange_payto_uri)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, +                               config_section, +                               "URL"); +    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 (NULL == +      (dbreset_proc = GNUNET_OS_start_process ( +         GNUNET_NO, +         GNUNET_OS_INHERIT_STD_ALL, +         NULL, NULL, NULL, +         "taler-bank-manage", +         "taler-bank-manage", +         "-c", config_filename, +         "--with-db", database, +         "django", +         "flush", +         "--no-input", NULL))) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to flush the bank db.\n"); +    GNUNET_free (database); +    GNUNET_CONFIGURATION_destroy (cfg); +    return GNUNET_SYSERR; +  } +  GNUNET_free (database); + +  if (GNUNET_SYSERR == +      GNUNET_OS_process_wait_status (dbreset_proc, +                                     &type, +                                     &code)) +  { +    GNUNET_OS_process_destroy (dbreset_proc); +    GNUNET_break (0); +    GNUNET_CONFIGURATION_destroy (cfg); +    return GNUNET_SYSERR; +  } +  if ( (type == GNUNET_OS_PROCESS_EXITED) && +       (0 != code) ) +  { +    fprintf (stderr, +             "Failed to setup database\n"); +    GNUNET_break (0); +    GNUNET_CONFIGURATION_destroy (cfg); +    return GNUNET_SYSERR; +  } +  if ( (type != GNUNET_OS_PROCESS_EXITED) || +       (0 != code) ) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected error running `taler-bank-manage django flush'!\n"); +    GNUNET_break (0); +    GNUNET_CONFIGURATION_destroy (cfg); +    return GNUNET_SYSERR; +  } +  GNUNET_OS_process_destroy (dbreset_proc); +  if (GNUNET_OK != +      TALER_BANK_auth_parse_cfg (cfg, +                                 config_section, +                                 &bc->exchange_auth)) +  { +    GNUNET_break (0); +    GNUNET_CONFIGURATION_destroy (cfg); +    return GNUNET_SYSERR; +  } +  GNUNET_CONFIGURATION_destroy (cfg); +  bc->exchange_payto = exchange_payto_uri; +  bc->user42_payto = "payto://x-taler-bank/localhost/42"; +  bc->user43_payto = "payto://x-taler-bank/localhost/43"; +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "Using pybank %s on port %u\n", +              bc->exchange_auth.wire_gateway_url, +              (unsigned int) port); +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, "exchange payto: %s\n", +              bc->exchange_payto); +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, "user42_payto: %s\n", +              bc->user42_payto); +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, "user42_payto: %s\n", +              bc->user43_payto); +  return GNUNET_OK; +} + + +/** + * Prepare launching a fakebank.  Check that the configuration + * file has the right option, and that the port is available. + * If everything is OK, return the configuration data of the fakebank. + * + * @param config_filename configuration file to use + * @param config_section which account to use (must match x-taler-bank) + * @param[out] bc set to the bank's configuration data + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_prepare_fakebank (const char *config_filename, +                                const char *config_section, +                                struct TALER_TESTING_BankConfiguration *bc) +{ +  struct GNUNET_CONFIGURATION_Handle *cfg; +  unsigned long long fakebank_port; +  char *exchange_payto_uri; + +  cfg = GNUNET_CONFIGURATION_create (); +  if (GNUNET_OK != GNUNET_CONFIGURATION_load (cfg, +                                              config_filename)) +    return GNUNET_SYSERR; + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_number (cfg, +                                             "BANK", +                                             "HTTP_PORT", +                                             &fakebank_port)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, +                               "BANK", +                               "HTTP_PORT"); +    GNUNET_CONFIGURATION_destroy (cfg); +    return GNUNET_SYSERR; +  } +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_string (cfg, +                                             config_section, +                                             "URL", /* FIXME: config should be renamed to payto_uri, it's not an url even! */ +                                             &exchange_payto_uri)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, +                               config_section, +                               "URL"); +    GNUNET_CONFIGURATION_destroy (cfg); +    return GNUNET_SYSERR; +  } +  bc->exchange_auth.method = TALER_BANK_AUTH_NONE; +  { +    char *exchange_xtalerbank_account; + +    exchange_xtalerbank_account +      = TALER_xtalerbank_account_from_payto (exchange_payto_uri); +    if (NULL == exchange_xtalerbank_account) +    { +      GNUNET_break (0); +      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; +    return GNUNET_SYSERR; +  } +  /* Now we know it's the fake bank, for purpose of authentication, we +   * don't have any auth. */ +  bc->exchange_auth.method = TALER_BANK_AUTH_NONE; +  bc->exchange_payto = exchange_payto_uri; +  bc->user42_payto = "payto://x-taler-bank/localhost/42"; +  bc->user43_payto = "payto://x-taler-bank/localhost/43"; +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, "exchange payto: %s\n", +              bc->exchange_payto); +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, "user42_payto: %s\n", +              bc->user42_payto); +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, "user42_payto: %s\n", +              bc->user43_payto); +  return GNUNET_OK; +} + + +/** + * Allocate and return a piece of wire-details.  Combines + * a @a payto -URL and adds some salt to create the JSON. + * + * @param payto payto://-URL to encapsulate + * @return JSON describing the account, including the + *         payto://-URL of the account, must be manually decref'd + */ +json_t * +TALER_TESTING_make_wire_details (const char *payto) +{ +  return json_pack ("{s:s, s:s}", +                    "url", payto, +                    "salt", +                    "test-salt (must be constant for aggregation tests)"); +} + + +/* end of testing_api_helpers_bank.c */ diff --git a/src/testing/testing_api_helpers_exchange.c b/src/testing/testing_api_helpers_exchange.c new file mode 100644 index 00000000..29c96db1 --- /dev/null +++ b/src/testing/testing_api_helpers_exchange.c @@ -0,0 +1,975 @@ +/* +  This file is part of TALER +  Copyright (C) 2018-2020 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as +  published by the Free Software Foundation; either version 3, or +  (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/testing_api_helpers_exchange.c + * @brief helper functions + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * Remove files from previous runs + * + * @param config_name configuration filename. + */ +void +TALER_TESTING_cleanup_files (const char *config_name) +{ +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_parse_and_run (config_name, +                                          &TALER_TESTING_cleanup_files_cfg, +                                          NULL)) +    exit (77); +} + + +/** + * Remove files from previous runs + * + * @param cls NULL + * @param cfg configuration + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_cleanup_files_cfg (void *cls, +                                 const struct GNUNET_CONFIGURATION_Handle *cfg) +{ +  char *dir; + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_filename (cfg, +                                               "exchange", +                                               "KEYDIR", +                                               &dir)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                               "exchange", +                               "KEYDIR"); +    return GNUNET_SYSERR; +  } +  if (GNUNET_YES == +      GNUNET_DISK_directory_test (dir, +                                  GNUNET_NO)) +    GNUNET_break (GNUNET_OK == +                  GNUNET_DISK_directory_remove (dir)); +  GNUNET_free (dir); +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_filename (cfg, +                                               "exchange", +                                               "REVOCATION_DIR", +                                               &dir)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                               "exchange", +                               "REVOCATION_DIR"); +    return GNUNET_SYSERR; +  } +  if (GNUNET_YES == +      GNUNET_DISK_directory_test (dir, +                                  GNUNET_NO)) +    GNUNET_break (GNUNET_OK == +                  GNUNET_DISK_directory_remove (dir)); +  GNUNET_free (dir); +  return GNUNET_OK; +} + + +/** + * Run `taler-exchange-keyup`. + * + * @param config_filename configuration file to use + * @param output_filename where to write the output for the auditor + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_run_keyup (const char *config_filename, +                         const char *output_filename) +{ +  struct GNUNET_OS_Process *proc; + +  proc = GNUNET_OS_start_process (GNUNET_NO, +                                  GNUNET_OS_INHERIT_STD_ALL, +                                  NULL, NULL, NULL, +                                  "taler-exchange-keyup", +                                  "taler-exchange-keyup", +                                  "-c", config_filename, +                                  "-o", output_filename, +                                  NULL); +  if (NULL == proc) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to run `taler-exchange-keyup`, is your PATH correct?\n"); +    return GNUNET_SYSERR; +  } +  GNUNET_OS_process_wait (proc); +  GNUNET_OS_process_destroy (proc); +  return GNUNET_OK; +} + + +/** + * Run `taler-auditor-sign`. + * + * @param config_filename configuration file to use + * @param exchange_master_pub master public key of the exchange + * @param auditor_base_url what is the base URL of the auditor + * @param signdata_in where is the information from taler-exchange-keyup + * @param signdata_out where to write the output for the exchange + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_run_auditor_sign (const char *config_filename, +                                const char *exchange_master_pub, +                                const char *auditor_base_url, +                                const char *signdata_in, +                                const char *signdata_out) +{ +  struct GNUNET_OS_Process *proc; + +  proc = GNUNET_OS_start_process (GNUNET_NO, +                                  GNUNET_OS_INHERIT_STD_ALL, +                                  NULL, NULL, NULL, +                                  "taler-auditor-sign", +                                  "taler-auditor-sign", +                                  "-c", config_filename, +                                  "-u", auditor_base_url, +                                  "-m", exchange_master_pub, +                                  "-r", signdata_in, +                                  "-o", signdata_out, +                                  NULL); +  if (NULL == proc) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to run `taler-auditor-sign`, is your PATH correct?\n"); +    return GNUNET_SYSERR; +  } +  GNUNET_OS_process_wait (proc); +  GNUNET_OS_process_destroy (proc); +  return GNUNET_OK; +} + + +/** + * Run `taler-auditor-exchange`. + * + * @param config_filename configuration file to use + * @param exchange_master_pub master public key of the exchange + * @param exchange_base_url what is the base URL of the exchange + * @param do_remove #GNUNET_NO to add exchange, #GNUNET_YES to remove + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_run_auditor_exchange (const char *config_filename, +                                    const char *exchange_master_pub, +                                    const char *exchange_base_url, +                                    int do_remove) +{ +  struct GNUNET_OS_Process *proc; +  enum GNUNET_OS_ProcessStatusType type; +  unsigned long code; + +  TALER_LOG_DEBUG ("Add exchange (%s,%s) to the auditor\n", +                   exchange_base_url, +                   exchange_master_pub); + +  proc = GNUNET_OS_start_process (GNUNET_NO, +                                  GNUNET_OS_INHERIT_STD_ALL, +                                  NULL, NULL, NULL, +                                  "taler-auditor-exchange", +                                  "taler-auditor-exchange", +                                  "-c", config_filename, +                                  "-u", exchange_base_url, +                                  "-m", exchange_master_pub, +                                  (GNUNET_YES == do_remove) +                                  ? "-r" +                                  : NULL, +                                  NULL); +  if (NULL == proc) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to run `taler-auditor-exchange`, is your PATH correct?\n"); +    return GNUNET_SYSERR; +  } +  GNUNET_assert (GNUNET_OK == +                 GNUNET_OS_process_wait_status (proc, +                                                &type, +                                                &code)); +  GNUNET_OS_process_destroy (proc); +  if ( (0 != code) || +       (GNUNET_OS_PROCESS_EXITED != type) ) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "taler-auditor-exchange terminated with error (%d/%d)\n", +                (int) type, +                (int) code); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * Run `taler-exchange-dbinit -r` (reset exchange database). + * + * @param config_filename configuration file to use + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_exchange_db_reset (const char *config_filename) +{ +  struct GNUNET_OS_Process *proc; +  enum GNUNET_OS_ProcessStatusType type; +  unsigned long code; + +  proc = GNUNET_OS_start_process (GNUNET_NO, +                                  GNUNET_OS_INHERIT_STD_ALL, +                                  NULL, NULL, NULL, +                                  "taler-exchange-dbinit", +                                  "taler-exchange-dbinit", +                                  "-c", config_filename, +                                  "-r", +                                  NULL); +  if (NULL == proc) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to run `taler-exchange-dbinit`, is your PATH correct?\n"); +    return GNUNET_NO; +  } +  if (GNUNET_SYSERR == +      GNUNET_OS_process_wait_status (proc, +                                     &type, +                                     &code)) +  { +    GNUNET_break (0); +    GNUNET_OS_process_destroy (proc); +    return GNUNET_SYSERR; +  } +  GNUNET_OS_process_destroy (proc); +  if ( (type == GNUNET_OS_PROCESS_EXITED) && +       (0 != code) ) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to setup (exchange) database, exit code %d\n", +                (int) code); +    return GNUNET_NO; +  } +  if ( (type != GNUNET_OS_PROCESS_EXITED) || +       (0 != code) ) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected error (%d/%d) running `taler-exchange-dbinit'!\n", +                (int) type, +                (int) code); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * Run `taler-auditor-dbinit -r` (reset auditor database). + * + * @param config_filename configuration file to use + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_auditor_db_reset (const char *config_filename) +{ +  struct GNUNET_OS_Process *proc; +  enum GNUNET_OS_ProcessStatusType type; +  unsigned long code; + +  proc = GNUNET_OS_start_process (GNUNET_NO, +                                  GNUNET_OS_INHERIT_STD_ALL, +                                  NULL, NULL, NULL, +                                  "taler-auditor-dbinit", +                                  "taler-auditor-dbinit", +                                  "-c", config_filename, +                                  "-r", +                                  NULL); +  if (NULL == proc) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to run `taler-auditor-dbinit`, is your PATH correct?\n"); +    return GNUNET_NO; +  } +  if (GNUNET_SYSERR == +      GNUNET_OS_process_wait_status (proc, +                                     &type, +                                     &code)) +  { +    GNUNET_break (0); +    GNUNET_OS_process_destroy (proc); +    return GNUNET_SYSERR; +  } +  GNUNET_OS_process_destroy (proc); +  if ( (type == GNUNET_OS_PROCESS_EXITED) && +       (0 != code) ) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to setup (auditor) database, exit code %d\n", +                (int) code); +    return GNUNET_NO; +  } +  if ( (type != GNUNET_OS_PROCESS_EXITED) || +       (0 != code) ) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Unexpected error (%d/%d) running `taler-auditor-dbinit'!\n", +                (int) type, +                (int) code); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * Type of closure for + * #sign_keys_for_exchange. + */ +struct SignInfo +{ +  /** +   * Members will be set to the exchange configuration. +   */ +  struct TALER_TESTING_ExchangeConfiguration *ec; + +  /** +   * Name of the configuration file to use. +   */ +  const char *config_filename; + +  /** +   * Must be set to input file with the data to be signed before +   * calling #TALER_TESTING_sign_keys_for_exchange. +   */ +  const char *auditor_sign_input_filename; +}; + + +/** + * 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 paramters + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +static int +sign_keys_for_exchange (void *cls, +                        const struct GNUNET_CONFIGURATION_Handle *cfg) +{ +  struct SignInfo *si = cls; +  char *test_home_dir; +  char *signed_keys_out; +  char *exchange_master_pub; +  int ret; + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_string (cfg, +                                             "exchange", +                                             "BASE_URL", +                                             &si->ec->exchange_url)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, +                               "exchange", +                               "BASE_URL"); +    si->ec->exchange_url = NULL; +    return GNUNET_NO; +  } + +  if (GNUNET_OK != +      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_filename (cfg, +                                               "paths", +                                               "TALER_TEST_HOME", +                                               &test_home_dir)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                               "paths", +                               "TALER_TEST_HOME"); +    ret = GNUNET_SYSERR; +    goto fail; +  } + +  GNUNET_asprintf (&signed_keys_out, +                   "%s/.local/share/taler/auditors/auditor.out", +                   test_home_dir); +  GNUNET_free (test_home_dir); + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_string (cfg, +                                             "exchange", +                                             "MASTER_PUBLIC_KEY", +                                             &exchange_master_pub)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                               "exchange", +                               "MASTER_PUBLIC_KEY"); +    GNUNET_free (signed_keys_out); +    ret = GNUNET_SYSERR; +    goto fail; +  } +  if (GNUNET_OK != +      TALER_TESTING_run_auditor_exchange (si->config_filename, +                                          exchange_master_pub, +                                          si->ec->exchange_url, +                                          GNUNET_NO)) +  { +    GNUNET_free (signed_keys_out); +    ret = GNUNET_NO; +    goto fail; +  } + +  if (GNUNET_OK != +      TALER_TESTING_run_auditor_sign (si->config_filename, +                                      exchange_master_pub, +                                      si->ec->auditor_url, +                                      si->auditor_sign_input_filename, +                                      signed_keys_out)) +  { +    GNUNET_free (signed_keys_out); +    GNUNET_free (exchange_master_pub); +    ret = GNUNET_NO; +    goto fail; +  } +  GNUNET_free (signed_keys_out); +  GNUNET_free (exchange_master_pub); +  return GNUNET_OK; +fail: +  GNUNET_free (si->ec->exchange_url); +  GNUNET_free (si->ec->auditor_url); +  si->ec->exchange_url = NULL; +  si->ec->auditor_url = NULL; +  return ret; +} + + +/** + * Prepare launching an exchange.  Checks that the configured + * port is available, runs taler-exchange-keyup, + * taler-auditor-sign and taler-exchange-dbinit.  Does NOT + * launch the exchange process itself. + * + * @param config_filename configuration file to use + * @param[out] ec will be set to the exchange configuration data + * @return #GNUNET_OK on success, #GNUNET_NO if test should be + *         skipped, #GNUNET_SYSERR on test failure + */ +int +TALER_TESTING_prepare_exchange (const char *config_filename, +                                struct TALER_TESTING_ExchangeConfiguration *ec) +{ +  struct SignInfo si = { +    .config_filename = config_filename, +    .ec = ec, +    .auditor_sign_input_filename = "auditor.in" +  }; + +  if (GNUNET_OK != +      TALER_TESTING_run_keyup (config_filename, +                               si.auditor_sign_input_filename)) +    return GNUNET_NO; +  if (GNUNET_OK != +      TALER_TESTING_exchange_db_reset (config_filename)) +    return GNUNET_NO; +  if (GNUNET_OK != +      TALER_TESTING_auditor_db_reset (config_filename)) +    return GNUNET_NO; +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_parse_and_run (config_filename, +                                          &sign_keys_for_exchange, +                                          &si)) +    return GNUNET_NO; +  return GNUNET_OK; +} + + +/** + * Find denomination key matching the given amount. + * + * @param keys array of keys to search + * @param amount coin value to look for + * @return NULL if no matching key was found + */ +const struct TALER_EXCHANGE_DenomPublicKey * +TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys, +                       const struct TALER_Amount *amount) +{ +  struct GNUNET_TIME_Absolute now; +  struct TALER_EXCHANGE_DenomPublicKey *pk; +  char *str; + +  now = GNUNET_TIME_absolute_get (); +  for (unsigned int i = 0; i<keys->num_denom_keys; i++) +  { +    pk = &keys->denom_keys[i]; +    if ( (0 == TALER_amount_cmp (amount, +                                 &pk->value)) && +         (now.abs_value_us >= pk->valid_from.abs_value_us) && +         (now.abs_value_us < +          pk->withdraw_valid_until.abs_value_us) ) +      return pk; +  } +  /* do 2nd pass to check if expiration times are to blame for +   * failure */ +  str = TALER_amount_to_string (amount); +  for (unsigned int i = 0; i<keys->num_denom_keys; i++) +  { +    pk = &keys->denom_keys[i]; +    if ( (0 == TALER_amount_cmp (amount, +                                 &pk->value)) && +         ( (now.abs_value_us < pk->valid_from.abs_value_us) || +           (now.abs_value_us > +            pk->withdraw_valid_until.abs_value_us) ) ) +    { +      GNUNET_log +        (GNUNET_ERROR_TYPE_WARNING, +        "Have denomination key for `%s', but with wrong" +        " expiration range %llu vs [%llu,%llu)\n", +        str, +        (unsigned long long) now.abs_value_us, +        (unsigned long long) pk->valid_from.abs_value_us, +        (unsigned long long) +        pk->withdraw_valid_until.abs_value_us); +      GNUNET_free (str); +      return NULL; +    } +  } +  GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +              "No denomination key for amount %s found\n", +              str); +  GNUNET_free (str); +  return NULL; +} + + +/** + * Wait for the exchange to have started. Waits for at + * most 10s, after that returns 77 to indicate an error. + * + * @param base_url what URL should we expect the exchange + *        to be running at + * @return 0 on success + */ +int +TALER_TESTING_wait_exchange_ready (const char *base_url) +{ +  char *wget_cmd; +  unsigned int iter; + +  GNUNET_asprintf (&wget_cmd, +                   "wget -q -t 1 -T 1 %skeys -o /dev/null -O /dev/null", +                   base_url); // make sure ends with '/' +  /* give child time to start and bind against the socket */ +  fprintf (stderr, +           "Waiting for `taler-exchange-httpd' to be ready (check with: %s)\n", +           wget_cmd); +  iter = 0; +  do +  { +    if (10 == iter) +    { +      fprintf (stderr, +               "Failed to launch `taler-exchange-httpd' (or `wget')\n"); +      GNUNET_free (wget_cmd); +      return 77; +    } +    fprintf (stderr, ".\n"); +    sleep (1); +    iter++; +  } +  while (0 != system (wget_cmd)); +  GNUNET_free (wget_cmd); +  return 0; +} + + +/** + * Wait for the auditor to have started. Waits for at + * most 10s, after that returns 77 to indicate an error. + * + * @param base_url what URL should we expect the auditor + *        to be running at + * @return 0 on success + */ +int +TALER_TESTING_wait_auditor_ready (const char *base_url) +{ +  char *wget_cmd; +  unsigned int iter; + +  GNUNET_asprintf (&wget_cmd, +                   "wget -q -t 1 -T 1 %sversion -o /dev/null -O /dev/null", +                   base_url); // make sure ends with '/' +  /* give child time to start and bind against the socket */ +  fprintf (stderr, +           "Waiting for `taler-auditor-httpd' to be ready\n"); +  iter = 0; +  do +  { +    if (10 == iter) +    { +      fprintf (stderr, +               "Failed to launch `taler-auditor-httpd' (or `wget')\n"); +      GNUNET_free (wget_cmd); +      return 77; +    } +    fprintf (stderr, ".\n"); +    sleep (1); +    iter++; +  } +  while (0 != system (wget_cmd)); +  GNUNET_free (wget_cmd); +  return 0; +} + + +/** + * Initialize scheduler loop and curl context for the testcase + * including starting and stopping the exchange using the given + * configuration file. + * + * @param main_cb routine containing all the commands to run. + * @param main_cb_cls closure for @a main_cb, typically NULL. + * @param config_file configuration file for the test-suite. + * @return #GNUNET_OK if all is okay, != #GNUNET_OK otherwise. + *         non-#GNUNET_OK codes are #GNUNET_SYSERR most of the + *         time. + */ +int +TALER_TESTING_setup_with_exchange (TALER_TESTING_Main main_cb, +                                   void *main_cb_cls, +                                   const char *config_file) +{ +  struct TALER_TESTING_SetupContext setup_ctx = { +    .config_filename = config_file, +    .main_cb = main_cb, +    .main_cb_cls = main_cb_cls +  }; +  int result; + +  result = +    GNUNET_CONFIGURATION_parse_and_run (config_file, +                                        &TALER_TESTING_setup_with_exchange_cfg, +                                        &setup_ctx); +  if (GNUNET_OK != result) +    return result; +  return GNUNET_OK; +} + + +/** + * Initialize scheduler loop and curl context for the test case + * including starting and stopping the exchange using the given + * configuration file. + * + * @param cls must be a `struct TALER_TESTING_SetupContext *` + * @param cfg configuration to use. + * @return #GNUNET_OK if no errors occurred. + */ +int +TALER_TESTING_setup_with_exchange_cfg (void *cls, +                                       const struct +                                       GNUNET_CONFIGURATION_Handle *cfg) +{ +  const struct TALER_TESTING_SetupContext *setup_ctx = cls; +  struct GNUNET_OS_Process *exchanged; +  unsigned long long port; +  char *serve; +  char *base_url; +  int result; + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_string (cfg, +                                             "exchange", +                                             "SERVE", +                                             &serve)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                               "exchange", +                               "SERVE"); +    return GNUNET_NO; +  } + +  if (0 == strcmp ("tcp", serve)) +  { +    if (GNUNET_OK != +        GNUNET_CONFIGURATION_get_value_number (cfg, +                                               "exchange", +                                               "PORT", +                                               &port)) +    { +      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                                 "exchange", +                                 "PORT"); +      GNUNET_free (serve); +      return GNUNET_NO; +    } + +    if (GNUNET_OK != +        GNUNET_NETWORK_test_port_free (IPPROTO_TCP, +                                       (uint16_t) port)) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                  "Required port %llu not available, skipping.\n", +                  port); +      GNUNET_free (serve); +      return GNUNET_NO; +    } +  } +  GNUNET_free (serve); +  exchanged = GNUNET_OS_start_process (GNUNET_NO, +                                       GNUNET_OS_INHERIT_STD_ALL, +                                       NULL, NULL, NULL, +                                       "taler-exchange-httpd", +                                       "taler-exchange-httpd", +                                       "-c", setup_ctx->config_filename, +                                       "-i", +                                       NULL); + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_string (cfg, +                                             "exchange", +                                             "BASE_URL", +                                             &base_url)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                               "exchange", +                               "BASE_URL"); +    return GNUNET_NO; +  } + +  if (0 != TALER_TESTING_wait_exchange_ready (base_url)) +  { +    GNUNET_free (base_url); +    return 77; +  } +  GNUNET_free (base_url); + +  /* NOTE: this call blocks.  */ +  result = TALER_TESTING_setup (setup_ctx->main_cb, +                                setup_ctx->main_cb_cls, +                                setup_ctx->config_filename, +                                exchanged, +                                GNUNET_YES); +  GNUNET_break (0 == +                GNUNET_OS_process_kill (exchanged, +                                        SIGTERM)); +  GNUNET_break (GNUNET_OK == +                GNUNET_OS_process_wait (exchanged)); +  GNUNET_OS_process_destroy (exchanged); +  return result; +} + + +/** + * Initialize scheduler loop and curl context for the test case + * including starting and stopping the auditor and exchange using the + * given configuration file. + * + * @param cls must be a `struct TALER_TESTING_SetupContext *` + * @param cfg configuration to use. + * @return #GNUNET_OK if no errors occurred. + */ +int +TALER_TESTING_setup_with_auditor_and_exchange_cfg (void *cls, +                                                   const struct +                                                   GNUNET_CONFIGURATION_Handle * +                                                   cfg) +{ +  const struct TALER_TESTING_SetupContext *setup_ctx = cls; +  struct GNUNET_OS_Process *auditord; +  unsigned long long port; +  char *serve; +  char *base_url; +  int result; + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_string (cfg, +                                             "auditor", +                                             "SERVE", +                                             &serve)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                               "auditor", +                               "SERVE"); +    return GNUNET_NO; +  } + +  if (0 == strcmp ("tcp", serve)) +  { +    if (GNUNET_OK != +        GNUNET_CONFIGURATION_get_value_number (cfg, +                                               "auditor", +                                               "PORT", +                                               &port)) +    { +      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                                 "auditor", +                                 "PORT"); +      GNUNET_free (serve); +      return GNUNET_NO; +    } + +    if (GNUNET_OK != +        GNUNET_NETWORK_test_port_free (IPPROTO_TCP, +                                       (uint16_t) port)) +    { +      GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                  "Required port %llu not available, skipping.\n", +                  port); +      GNUNET_free (serve); +      return GNUNET_NO; +    } +  } +  GNUNET_free (serve); +  auditord = GNUNET_OS_start_process (GNUNET_NO, +                                      GNUNET_OS_INHERIT_STD_ALL, +                                      NULL, NULL, NULL, +                                      "taler-auditor-httpd", +                                      "taler-auditor-httpd", +                                      "-c", setup_ctx->config_filename, +                                      NULL); + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_string (cfg, +                                             "auditor", +                                             "BASE_URL", +                                             &base_url)) +  { +    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                               "auditor", +                               "BASE_URL"); +    return GNUNET_NO; +  } + +  if (0 != TALER_TESTING_wait_auditor_ready (base_url)) +  { +    GNUNET_free (base_url); +    GNUNET_break (0 == +                  GNUNET_OS_process_kill (auditord, +                                          SIGTERM)); +    GNUNET_break (GNUNET_OK == +                  GNUNET_OS_process_wait (auditord)); +    GNUNET_OS_process_destroy (auditord); +    return 77; +  } +  GNUNET_free (base_url); + +  /* NOTE: this call blocks.  */ +  result = TALER_TESTING_setup_with_exchange_cfg ((void *) setup_ctx, +                                                  cfg); +  GNUNET_break (0 == +                GNUNET_OS_process_kill (auditord, +                                        SIGTERM)); +  GNUNET_break (GNUNET_OK == +                GNUNET_OS_process_wait (auditord)); +  GNUNET_OS_process_destroy (auditord); +  return result; +} + + +/** + * Initialize scheduler loop and curl context for the test case + * including starting and stopping the auditor and exchange using the + * given configuration file. + * + * @param main_cb main method. + * @param main_cb_cls main method closure. + * @param config_file configuration file name.  Is is used + *        by both this function and the exchange itself.  In the + *        first case it gives out the exchange port number and + *        the exchange base URL so as to check whether the port + *        is available and the exchange responds when requested + *        at its base URL. + * @return #GNUNET_OK if no errors occurred. + */ +int +TALER_TESTING_setup_with_auditor_and_exchange (TALER_TESTING_Main main_cb, +                                               void *main_cb_cls, +                                               const char *config_file) +{ +  struct TALER_TESTING_SetupContext setup_ctx = { +    .config_filename = config_file, +    .main_cb = main_cb, +    .main_cb_cls = main_cb_cls +  }; + +  return GNUNET_CONFIGURATION_parse_and_run (config_file, +                                             & +                                             TALER_TESTING_setup_with_auditor_and_exchange_cfg, +                                             &setup_ctx); +} + + +/** + * Test port in URL string for availability. + * + * @param url URL to extract port from, 80 is default + * @return #GNUNET_OK if the port is free + */ +int +TALER_TESTING_url_port_free (const char *url) +{ +  const char *port; +  long pnum; + +  port = strrchr (url, +                  (unsigned char) ':'); +  if (NULL == port) +    pnum = 80; +  else +    pnum = strtol (port + 1, NULL, 10); +  if (GNUNET_OK != +      GNUNET_NETWORK_test_port_free (IPPROTO_TCP, +                                     pnum)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Port %u not available.\n", +                (unsigned int) pnum); +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/* end of testing_api_helpers_exchange.c */ diff --git a/src/testing/testing_api_loop.c b/src/testing/testing_api_loop.c new file mode 100644 index 00000000..a7a5a23a --- /dev/null +++ b/src/testing/testing_api_loop.c @@ -0,0 +1,828 @@ +/* +  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_loop.c + * @brief main interpreter loop for testcases + * @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" +#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 + */ +const struct TALER_TESTING_Command * +TALER_TESTING_interpreter_lookup_command (struct TALER_TESTING_Interpreter *is, +                                          const char *label) +{ +  if (NULL == label) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_WARNING, +                "Attempt to lookup command for empty label\n"); +    return NULL; +  } +  /* Search backwards as we most likely reference recent commands */ +  for (int i = is->ip; i >= 0; i--) +  { +    const struct TALER_TESTING_Command *cmd = &is->commands[i]; + +    /* Give precedence to top-level commands.  */ +    if ( (NULL != cmd->label) && +         (0 == strcmp (cmd->label, +                       label)) ) +      return cmd; + +    if (TALER_TESTING_cmd_is_batch (cmd)) +    { +#define BATCH_INDEX 1 +      struct TALER_TESTING_Command *batch; + +      GNUNET_assert (GNUNET_OK == +                     TALER_TESTING_get_trait_cmd (cmd, +                                                  BATCH_INDEX, +                                                  &batch)); +      for (unsigned int j = 0; +           NULL != (cmd = &batch[j])->label; +           j++) +      { +        if ( (NULL != cmd->label) && +             (0 == strcmp (cmd->label, +                           label)) ) +          return cmd; +      } +    } +  } +  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +              "Command not found: %s\n", +              label); +  return NULL; + +} + + +/** + * Obtain main execution context for the main loop. + */ +struct GNUNET_CURL_Context * +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) +{ +  return is->fakebank; +} + + +/** + * Run tests starting the "fakebank" first.  The "fakebank" + * is a C minimalist version of the human-oriented Python bank, + * which is also part of the Taler project. + * + * @param is pointer to the interpreter state + * @param commands the list of commands to execute + * @param bank_url the url the fakebank is supposed to run on + */ +void +TALER_TESTING_run_with_fakebank (struct TALER_TESTING_Interpreter *is, +                                 struct TALER_TESTING_Command *commands, +                                 const char *bank_url) +{ +  const char *port; +  long pnum; + +  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); +  is->fakebank = TALER_FAKEBANK_start ((uint16_t) pnum); +  if (NULL == is->fakebank) +  { +    GNUNET_break (0); +    is->result = GNUNET_SYSERR; +    return; +  } +  TALER_TESTING_run (is, +                     commands); +} + + +/** + * Run the main interpreter loop that performs exchange operations. + * + * @param cls contains the `struct InterpreterState` + */ +static void +interpreter_run (void *cls); + + +/** + * Current command is done, run the next one. + */ +void +TALER_TESTING_interpreter_next (struct TALER_TESTING_Interpreter *is) +{ +  static unsigned long long ipc; +  static struct GNUNET_TIME_Absolute last_report; +  struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; + +  if (GNUNET_SYSERR == is->result) +    return; /* ignore, we already failled! */ +  if (TALER_TESTING_cmd_is_batch (cmd)) +    TALER_TESTING_cmd_batch_next (is); +  else +    is->ip++; +  if (0 == (ipc % 1000)) +  { +    if (0 != ipc) +      GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, +                  "Interpreter executed 1000 instructions in %s\n", +                  GNUNET_STRINGS_relative_time_to_string ( +                    GNUNET_TIME_absolute_get_duration (last_report), +                    GNUNET_YES)); +    last_report = GNUNET_TIME_absolute_get (); +  } +  ipc++; +  is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, +                                       is); +} + + +/** + * Current command failed, clean up and fail the test case. + * + * @param is interpreter of the test + */ +void +TALER_TESTING_interpreter_fail (struct TALER_TESTING_Interpreter *is) +{ +  struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; + +  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +              "Failed at command `%s'\n", +              cmd->label); +  while (TALER_TESTING_cmd_is_batch (cmd)) +  { +    cmd = TALER_TESTING_cmd_batch_get_current (cmd); +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Batch is at command `%s'\n", +                cmd->label); +  } +  is->result = GNUNET_SYSERR; +  GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Create command array terminator. + * + * @return a end-command. + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_end (void) +{ +  static struct TALER_TESTING_Command cmd; +  cmd.label = NULL; + +  return cmd; +} + + +/** + * Obtain current label. + */ +const char * +TALER_TESTING_interpreter_get_current_label (struct +                                             TALER_TESTING_Interpreter *is) +{ +  struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; + +  return cmd->label; +} + + +/** + * Run the main interpreter loop that performs exchange operations. + * + * @param cls contains the `struct TALER_TESTING_Interpreter` + */ +static void +interpreter_run (void *cls) +{ +  struct TALER_TESTING_Interpreter *is = cls; +  struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; + +  is->task = NULL; + +  if (NULL == cmd->label) +  { + +    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +                "Running command END\n"); +    is->result = GNUNET_OK; +    GNUNET_SCHEDULER_shutdown (); +    return; +  } + +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "Running command `%s'\n", +              cmd->label); + +  cmd->run (cmd->cls, +            cmd, +            is); +} + + +/** + * Function run when the test terminates (good or bad). + * Cleans up our state. + * + * @param cls the interpreter state. + */ +static void +do_shutdown (void *cls) +{ +  struct TALER_TESTING_Interpreter *is = cls; +  struct TALER_TESTING_Command *cmd; +  const char *label; + +  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 != is->task) +  { +    GNUNET_SCHEDULER_cancel (is->task); +    is->task = NULL; +  } +  if (NULL != is->ctx) +  { +    GNUNET_CURL_fini (is->ctx); +    is->ctx = NULL; +  } +  if (NULL != is->rc) +  { +    GNUNET_CURL_gnunet_rc_destroy (is->rc); +    is->rc = 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) +  { +    TALER_FAKEBANK_stop (is->fakebank); +    is->fakebank = NULL; +  } +  GNUNET_free_non_null (is->commands); +} + + +/** + * Function run when the test terminates (good or bad) with timeout. + * + * @param cls NULL + */ +static void +do_timeout (void *cls) +{ +  struct TALER_TESTING_Interpreter *is = cls; + +  is->timeout_task = NULL; +  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +              "Terminating test due to timeout\n"); +  GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Task triggered whenever we receive a SIGCHLD (child + * process died). + * + * @param cls closure + */ +static void +maint_child_death (void *cls) +{ +  struct TALER_TESTING_Interpreter *is = cls; +  struct TALER_TESTING_Command *cmd = &is->commands[is->ip]; +  const struct GNUNET_DISK_FileHandle *pr; + +  struct GNUNET_OS_Process **processp; +  char c[16]; + +  if (TALER_TESTING_cmd_is_batch (cmd)) +  { +    struct TALER_TESTING_Command *batch_cmd; + +    GNUNET_assert +      (GNUNET_OK == TALER_TESTING_get_trait_cmd +        (cmd, 0, &batch_cmd)); /* bad? */ +    cmd = batch_cmd; +  } + +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "Got SIGCHLD for `%s'.\n", +              cmd->label); + +  is->child_death_task = NULL; +  pr = GNUNET_DISK_pipe_handle (sigpipe, +                                GNUNET_DISK_PIPE_END_READ); +  GNUNET_break (0 < +                GNUNET_DISK_file_read (pr, +                                       &c, +                                       sizeof (c))); +  if (GNUNET_OK != +      TALER_TESTING_get_trait_process (cmd, +                                       0, +                                       &processp)) +  { +    GNUNET_break (0); +    TALER_TESTING_interpreter_fail (is); +    return; +  } + +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "Got the dead child process handle" +              ", waiting for termination ...\n"); + +  GNUNET_OS_process_wait (*processp); +  GNUNET_OS_process_destroy (*processp); +  *processp = NULL; + +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "... definitively terminated\n"); + +  if (GNUNET_OK == is->reload_keys) +  { +    if (NULL == is->exchanged) +    { +      GNUNET_break (0); +    } +    else +    { +      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +                  "Triggering key state reload at exchange\n"); +      GNUNET_break (0 == GNUNET_OS_process_kill +                      (is->exchanged, SIGUSR1)); +      sleep (5); /* make sure signal was received and processed */ +    } +  } + +  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +              "Dead child, go on with next command.\n"); +  TALER_TESTING_interpreter_next (is); +} + + +/** + * Wait until we receive SIGCHLD signal. + * Then obtain the process trait of the current + * command, wait on the the zombie and continue + * with the next command. + */ +void +TALER_TESTING_wait_for_sigchld (struct TALER_TESTING_Interpreter *is) +{ +  const struct GNUNET_DISK_FileHandle *pr; + +  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); +} + + +/** + * Run the testsuite.  Note, CMDs are copied into + * the interpreter state because they are _usually_ + * defined into the "run" method that returns after + * having scheduled the test interpreter. + * + * @param is the interpreter state + * @param commands the list of command to execute + * @param timeout how long to wait + */ +void +TALER_TESTING_run2 (struct TALER_TESTING_Interpreter *is, +                    struct TALER_TESTING_Command *commands, +                    struct GNUNET_TIME_Relative timeout) +{ +  unsigned int i; + +  if (NULL != is->timeout_task) +  { +    GNUNET_SCHEDULER_cancel (is->timeout_task); +    is->timeout_task = NULL; +  } +  /* 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); +} + + +/** + * Run the testsuite.  Note, CMDs are copied into + * the interpreter state because they are _usually_ + * defined into the "run" method that returns after + * having scheduled the test interpreter. + * + * @param is the interpreter state + * @param commands the list of command to execute + */ +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)); +} + + +/** + * Information used by the wrapper around the main + * "run" method. + */ +struct MainContext +{ +  /** +   * Main "run" method. +   */ +  TALER_TESTING_Main main_cb; + +  /** +   * Closure for @e main_cb. +   */ +  void *main_cb_cls; + +  /** +   * Interpreter state. +   */ +  struct TALER_TESTING_Interpreter *is; + +  /** +   * Configuration filename.  The wrapper uses it to fetch +   * the exchange port number; We could have passed the port +   * number here, but having the config filename seems more +   * generic. +   */ +  const char *config_filename; + +  /** +   * URL of the exchange. +   */ +  char *exchange_url; + +}; + + +/** + * Signal handler called for SIGCHLD.  Triggers the + * respective handler by writing to the trigger pipe. + */ +static void +sighandler_child_death () +{ +  static char c; +  int old_errno = errno;  /* back-up errno */ + +  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 */ +} + + +/** + * "Canonical" cert_cb used when we are connecting to the + * Exchange. + * + * @param cls closure, typically, the "run" method containing + *        all the commands to be run, and a closure for it. + * @param keys the exchange's keys. + * @param compat protocol compatibility information. + */ +void +TALER_TESTING_cert_cb (void *cls, +                       const struct TALER_EXCHANGE_Keys *keys, +                       enum TALER_EXCHANGE_VersionCompatibility compat) +{ +  struct MainContext *main_ctx = cls; +  struct TALER_TESTING_Interpreter *is = main_ctx->is; + +  if (NULL == keys) +  { +    if (GNUNET_NO == is->working) +    { +      GNUNET_log +        (GNUNET_ERROR_TYPE_WARNING, +        "Got NULL response for /keys" +        " during startup, retrying!\n"); +      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!\n"); +  } +  else +  { +    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, +                "Got %d DK from /keys in generation %u\n", +                keys->num_denom_keys, +                is->key_generation + 1); +  } +  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; + +  /* Very first start of tests, call "run()" */ +  if (1 == is->key_generation) +  { +    main_ctx->main_cb (main_ctx->main_cb_cls, +                       is); +    return; +  } + +  /* Tests already started, just trigger the +   * next command. */ +  TALER_LOG_DEBUG ("Cert_cb, scheduling CMD (ip: %d)\n", +                   is->ip); +  GNUNET_SCHEDULER_add_now (&interpreter_run, +                            is); +} + + +/** + * 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 +main_wrapper_exchange_agnostic (void *cls) +{ +  struct MainContext *main_ctx = cls; + +  main_ctx->main_cb (main_ctx->main_cb_cls, +                     main_ctx->is); +} + + +/** + * Function run when the test is aborted before we launch the actual + * interpreter.  Cleans up our state. + * + * @param cls the main context + */ +static void +do_abort (void *cls) +{ +  struct MainContext *main_ctx = cls; +  struct TALER_TESTING_Interpreter *is = main_ctx->is; + +  is->timeout_task = NULL; +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "Executing abort prior to interpreter launch\n"); +  if (NULL != is->exchange) +  { +    TALER_EXCHANGE_disconnect (is->exchange); +    is->exchange = NULL; +  } +} + + +/** + * Initialize scheduler loop and curl context for the testcase, + * and responsible to run the "run" method. + * + * @param cls a `struct MainContext *` + * @param cfg configuration to use + */ +static int +main_exchange_connect_with_cfg (void *cls, +                                const struct GNUNET_CONFIGURATION_Handle *cfg) +{ +  struct MainContext *main_ctx = cls; +  struct TALER_TESTING_Interpreter *is = main_ctx->is; +  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 GNUNET_SYSERR; +  } +  main_ctx->exchange_url = exchange_url; +  is->cfg = cfg; +  is->timeout_task = GNUNET_SCHEDULER_add_shutdown (&do_abort, +                                                    main_ctx); +  GNUNET_break +    (NULL != (is->exchange = TALER_EXCHANGE_connect +                               (is->ctx, +                               exchange_url, +                               &TALER_TESTING_cert_cb, +                               main_ctx, +                               TALER_EXCHANGE_OPTION_END))); +  is->cfg = NULL; +  return GNUNET_OK; +} + + +/** + * 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 MainContext *main_ctx = cls; + +  GNUNET_break (GNUNET_OK == +                GNUNET_CONFIGURATION_parse_and_run (main_ctx->config_filename, +                                                    & +                                                    main_exchange_connect_with_cfg, +                                                    main_ctx)); +} + + +/** + * 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. + * @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. + */ +int +TALER_TESTING_setup (TALER_TESTING_Main main_cb, +                     void *main_cb_cls, +                     const char *config_filename, +                     struct GNUNET_OS_Process *exchanged, +                     int exchange_connect) +{ +  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, +    /* needed to read values like exchange port +     * number to construct the exchange url.*/ +    .config_filename = config_filename +  }; +  struct GNUNET_SIGNAL_Context *shc_chld; + +  memset (&is, +          0, +          sizeof (is)); +  is.exchanged = exchanged; +  sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, +                              GNUNET_NO, GNUNET_NO); +  GNUNET_assert (NULL != sigpipe); +  shc_chld = GNUNET_SIGNAL_handler_install +               (GNUNET_SIGCHLD, +               &sighandler_child_death); +  is.ctx = GNUNET_CURL_init +             (&GNUNET_CURL_gnunet_scheduler_reschedule, +             &is.rc); +  GNUNET_CURL_enable_async_scope_header (is.ctx, "Taler-Correlation-Id"); +  GNUNET_assert (NULL != is.ctx); +  is.rc = GNUNET_CURL_gnunet_rc_create (is.ctx); + +  /* Blocking */ + +  if (GNUNET_YES == exchange_connect) +    GNUNET_SCHEDULER_run (&main_wrapper_exchange_connect, +                          &main_ctx); +  else +    GNUNET_SCHEDULER_run (&main_wrapper_exchange_agnostic, +                          &main_ctx); +  if (NULL != is.final_cleanup_cb) +    is.final_cleanup_cb (is.final_cleanup_cb_cls); +  GNUNET_free_non_null (main_ctx.exchange_url); +  GNUNET_SIGNAL_handler_uninstall (shc_chld); +  GNUNET_DISK_pipe_close (sigpipe); +  sigpipe = NULL; +  return is.result; +} + + +/* end of testing_api_loop.c */ diff --git a/src/testing/testing_api_trait_amount.c b/src/testing/testing_api_trait_amount.c new file mode 100644 index 00000000..96698b49 --- /dev/null +++ b/src/testing/testing_api_trait_amount.c @@ -0,0 +1,76 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as +  published by the Free Software Foundation; either version 3, +  or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_trait_amount.c + * @brief offer amounts as traits. + * @author Marcello Stanisci + */ + +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_AMOUNT "amount" + +/** + * Obtain an amount from a @a cmd. + * + * @param cmd command to extract the amount from. + * @param index which amount to pick if @a cmd has multiple + *        on offer + * @param[out] amount set to the amount. + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_get_trait_amount_obj (const struct TALER_TESTING_Command *cmd, +                                    unsigned int index, +                                    const struct TALER_Amount **amount) +{ +  return cmd->traits (cmd->cls, +                      (const void **) amount, +                      TALER_TESTING_TRAIT_AMOUNT, +                      index); +} + + +/** + * Offer amount. + * + * @param index which amount to offer, in case there are + *        multiple available. + * @param amount the amount to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_amount_obj (unsigned int index, +                                     const struct TALER_Amount *amount) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_AMOUNT, +    .ptr = (const void *) amount +  }; + +  return ret; +} + + +/* end of testing_api_trait_amount.c */ diff --git a/src/testing/testing_api_trait_blinding_key.c b/src/testing/testing_api_trait_blinding_key.c new file mode 100644 index 00000000..ae1889a1 --- /dev/null +++ b/src/testing/testing_api_trait_blinding_key.c @@ -0,0 +1,77 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_trait_blinding_key.c + * @brief offer blinding keys as traits. + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_BLINDING_KEY "blinding-key" + + +/** + * Obtain a blinding key from a @a cmd. + * + * @param cmd command to extract trait from + * @param index which coin to pick if @a cmd has multiple on offer. + * @param[out] blinding_key set to the offered blinding key. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_blinding_key +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  const struct TALER_DenominationBlindingKeyP **blinding_key) +{ +  return cmd->traits (cmd->cls, +                      (const void **) blinding_key, +                      TALER_TESTING_TRAIT_BLINDING_KEY, +                      index); +} + + +/** + * Offer blinding key. + * + * @param index index number to associate to the offered key. + * @param blinding_key blinding key to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_blinding_key +  (unsigned int index, +  const struct TALER_DenominationBlindingKeyP *blinding_key) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_BLINDING_KEY, +    .ptr = (const void *) blinding_key +  }; + +  return ret; +} + + +/* end of testing_api_trait_blinding_key.c */ diff --git a/src/testing/testing_api_trait_cmd.c b/src/testing/testing_api_trait_cmd.c new file mode 100644 index 00000000..f2405471 --- /dev/null +++ b/src/testing/testing_api_trait_cmd.c @@ -0,0 +1,80 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +  General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/testing_api_trait_cmd.c + * @brief offers CMDs as traits. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_CMD "cmd" + + +/** + * Obtain a command from @a cmd. + * + * @param cmd command to extract the command from. + * @param index always zero.  Commands offering this + *        kind of traits do not need this index.  For + *        example, a "batch" CMD returns always the + *        CMD currently being executed. + * @param[out] _cmd where to write the wire details. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_cmd (const struct TALER_TESTING_Command *cmd, +                             unsigned int index, +                             struct TALER_TESTING_Command **_cmd) +{ +  return cmd->traits (cmd->cls, +                      (const void **) _cmd, +                      TALER_TESTING_TRAIT_CMD, +                      index); +} + + +/** + * Offer a command in a trait. + * + * @param index always zero.  Commands offering this + *        kind of traits do not need this index.  For + *        example, a "meta" CMD returns always the + *        CMD currently being executed. + * @param cmd wire details to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_cmd (unsigned int index, +                              const struct TALER_TESTING_Command *cmd) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_CMD, +    .ptr = (const struct TALER_TESTING_Command *) cmd +  }; +  return ret; +} + + +/* end of testing_api_trait_cmd.c */ diff --git a/src/testing/testing_api_trait_coin_priv.c b/src/testing/testing_api_trait_coin_priv.c new file mode 100644 index 00000000..61a770cf --- /dev/null +++ b/src/testing/testing_api_trait_coin_priv.c @@ -0,0 +1,78 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published by +  the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +  General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/testing_api_trait_coin_priv.c + * @brief coin priv traits. + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_COIN_PRIVATE_KEY "coin-private-key" + + +/** + * Obtain a coin private key from a @a cmd. + * + * @param cmd command to extract trait from. + * @param index index of the coin priv to obtain. + * @param[out] coin_priv set to the private key of the coin. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_coin_priv +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  const struct TALER_CoinSpendPrivateKeyP **coin_priv) +{ +  return cmd->traits (cmd->cls, +                      (const void **) coin_priv, +                      TALER_TESTING_TRAIT_COIN_PRIVATE_KEY, +                      index); +} + + +/** + * Offer coin private key. + * + * @param index index number to associate with offered coin priv. + * @param coin_priv coin private key to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_coin_priv +  (unsigned int index, +  const struct TALER_CoinSpendPrivateKeyP *coin_priv) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_COIN_PRIVATE_KEY, +    .ptr = (const void *) coin_priv +  }; + +  return ret; +} + + +/* end of testing_api_trait_coin_priv.c */ diff --git a/src/testing/testing_api_trait_contract.c b/src/testing/testing_api_trait_contract.c new file mode 100644 index 00000000..1e88cb86 --- /dev/null +++ b/src/testing/testing_api_trait_contract.c @@ -0,0 +1,74 @@ +/* +  This file is part of TALER +  Copyright (C) 2018-2020 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +  General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_trait_contract.c + * @brief offers contract term trait. + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_testing_lib.h" + + +/** + * Contains a contract terms object as a json_t. + */ +#define TALER_TESTING_TRAIT_CONTRACT_TERMS "contract-terms" + + +/** + * Obtain contract terms from @a cmd. + * + * @param cmd command to extract the contract terms from. + * @param index contract terms index number. + * @param[out] contract_terms where to write the contract terms. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_contract_terms (const struct TALER_TESTING_Command *cmd, +                                        unsigned int index, +                                        const json_t **contract_terms) +{ +  return cmd->traits (cmd->cls, +                      (const void **) contract_terms, +                      TALER_TESTING_TRAIT_CONTRACT_TERMS, +                      index); +} + + +/** + * Offer contract terms. + * + * @param index contract terms index number. + * @param contract_terms contract terms to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_contract_terms (unsigned int index, +                                         const json_t *contract_terms) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_CONTRACT_TERMS, +    .ptr = (const void *) contract_terms +  }; +  return ret; +} diff --git a/src/testing/testing_api_trait_denom_pub.c b/src/testing/testing_api_trait_denom_pub.c new file mode 100644 index 00000000..f866588d --- /dev/null +++ b/src/testing/testing_api_trait_denom_pub.c @@ -0,0 +1,77 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_trait_denom_pub.c + * @brief denom pub traits. + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_DENOM_PUB "denomination-public-key" + + +/** + * Obtain a denomination public key from a @a cmd. + * + * @param cmd command to extract trait from + * @param index index number of the denom to obtain. + * @param[out] denom_pub set to the offered denom pub. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_denom_pub (const struct TALER_TESTING_Command *cmd, +                                   unsigned int index, +                                   const struct +                                   TALER_EXCHANGE_DenomPublicKey **denom_pub) +{ +  return cmd->traits (cmd->cls, +                      (const void **) denom_pub, +                      TALER_TESTING_TRAIT_DENOM_PUB, +                      index); +} + + +/** + * Make a trait for a denomination public key. + * + * @param index index number to associate to the offered denom pub. + * @param denom_pub denom pub to offer with this trait. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_denom_pub (unsigned int index, +                                    const struct +                                    TALER_EXCHANGE_DenomPublicKey *denom_pub) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_DENOM_PUB, +    .ptr = (const void *) denom_pub +  }; + +  return ret; +} + + +/* end of testing_api_trait_denom_pub.c */ diff --git a/src/testing/testing_api_trait_denom_sig.c b/src/testing/testing_api_trait_denom_sig.c new file mode 100644 index 00000000..07e89440 --- /dev/null +++ b/src/testing/testing_api_trait_denom_sig.c @@ -0,0 +1,79 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as +  published by the Free Software Foundation; either version 3, +  or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/testing_api_trait_denom_sig.c + * @brief offer denomination signatures as traits + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_DENOM_SIG "denomination-signature" + + +/** + * Obtain a denomination signature from a @a cmd. + * + * @param cmd command to extract the denom sig from. + * @param index index number associated with the denom sig. + * @param[out] denom_sig set to the offered signature. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_denom_sig +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  const struct TALER_DenominationSignature **denom_sig) +{ +  return cmd->traits (cmd->cls, +                      (const void **) denom_sig, +                      TALER_TESTING_TRAIT_DENOM_SIG, +                      index); +} + + +/** + * Offer denom sig. + * + * @param index index number to associate to the signature on + *        offer. + * @param denom_sig the denom sig on offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_denom_sig +  (unsigned int index, +  const struct TALER_DenominationSignature *denom_sig) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_DENOM_SIG, +    .ptr = (const void *) denom_sig +  }; + +  return ret; +} + + +/* end of testing_api_trait_denom_sig.c */ diff --git a/src/testing/testing_api_trait_exchange_pub.c b/src/testing/testing_api_trait_exchange_pub.c new file mode 100644 index 00000000..8c702726 --- /dev/null +++ b/src/testing/testing_api_trait_exchange_pub.c @@ -0,0 +1,77 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_trait_exchange_pub.c + * @brief exchange pub traits. + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_EXCHANGE_PUB "exchange-public-key" + + +/** + * Obtain a exchange public key from a @a cmd. + * + * @param cmd command to extract trait from + * @param index index number of the exchange to obtain. + * @param[out] exchange_pub set to the offered exchange pub. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_exchange_pub +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  const struct TALER_ExchangePublicKeyP **exchange_pub) +{ +  return cmd->traits (cmd->cls, +                      (const void **) exchange_pub, +                      TALER_TESTING_TRAIT_EXCHANGE_PUB, +                      index); +} + + +/** + * Make a trait for a exchange public key. + * + * @param index index number to associate to the offered exchange pub. + * @param exchange_pub exchange pub to offer with this trait. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_exchange_pub +  (unsigned int index, +  const struct TALER_ExchangePublicKeyP *exchange_pub) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_EXCHANGE_PUB, +    .ptr = (const void *) exchange_pub +  }; + +  return ret; +} + + +/* end of testing_api_trait_exchange_pub.c */ diff --git a/src/testing/testing_api_trait_exchange_sig.c b/src/testing/testing_api_trait_exchange_sig.c new file mode 100644 index 00000000..349454ae --- /dev/null +++ b/src/testing/testing_api_trait_exchange_sig.c @@ -0,0 +1,77 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_trait_exchange_sig.c + * @brief exchange pub traits. + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_EXCHANGE_SIG "exchange-online-signature" + + +/** + * Obtain a exchange signature (online sig) from a @a cmd. + * + * @param cmd command to extract trait from + * @param index index number of the exchange to obtain. + * @param[out] exchange_sig set to the offered exchange signature. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_exchange_sig +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  const struct TALER_ExchangeSignatureP **exchange_sig) +{ +  return cmd->traits (cmd->cls, +                      (const void **) exchange_sig, +                      TALER_TESTING_TRAIT_EXCHANGE_SIG, +                      index); +} + + +/** + * Make a trait for a exchange signature. + * + * @param index index number to associate to the offered exchange pub. + * @param exchange_sig exchange signature to offer with this trait. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_exchange_sig +  (unsigned int index, +  const struct TALER_ExchangeSignatureP *exchange_sig) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_EXCHANGE_SIG, +    .ptr = (const void *) exchange_sig +  }; + +  return ret; +} + + +/* end of testing_api_trait_exchange_sig.c */ diff --git a/src/testing/testing_api_trait_fresh_coin.c b/src/testing/testing_api_trait_fresh_coin.c new file mode 100644 index 00000000..e5f1c682 --- /dev/null +++ b/src/testing/testing_api_trait_fresh_coin.c @@ -0,0 +1,77 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +  General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_trait_fresh_coin.c + * @brief traits to offer fresh conins (after "melt" operations) + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_FRESH_COINS "fresh-coins" + +/** + * Get a array of fresh coins. + * + * @param cmd command to extract the fresh coin from. + * @param index which array to pick if @a cmd has multiple + *        on offer. + * @param[out] fresh_coins will point to the offered array. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_fresh_coins +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  const struct TALER_TESTING_FreshCoinData **fresh_coins) +{ +  return cmd->traits (cmd->cls, +                      (const void **) fresh_coins, +                      TALER_TESTING_TRAIT_FRESH_COINS, +                      index); +} + + +/** + * Offer a _array_ of fresh coins. + * + * @param index which array of fresh coins to offer, + *        if there are multiple on offer.  Typically passed as + *        zero. + * @param fresh_coins the array of fresh coins to offer + * @return the trait, + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_fresh_coins +  (unsigned int index, +  const struct TALER_TESTING_FreshCoinData *fresh_coins) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_FRESH_COINS, +    .ptr = (const void *) fresh_coins +  }; +  return ret; +} + + +/* end of testing_api_trait_fresh_coin.c */ diff --git a/src/testing/testing_api_trait_json.c b/src/testing/testing_api_trait_json.c new file mode 100644 index 00000000..cbddad53 --- /dev/null +++ b/src/testing/testing_api_trait_json.c @@ -0,0 +1,123 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +  General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/testing_api_trait_json.c + * @brief offers JSON traits. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_WIRE_DETAILS "wire-details" +#define TALER_TESTING_TRAIT_EXCHANGE_KEYS "exchange-keys" + +/** + * Obtain serialized exchange keys from @a cmd. + * + * @param cmd command to extract the keys from. + * @param index index number associate with the keys on offer. + * @param[out] keys where to write the serialized keys. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_exchange_keys +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  const json_t **keys) +{ +  return cmd->traits (cmd->cls, +                      (const void **) keys, +                      TALER_TESTING_TRAIT_EXCHANGE_KEYS, +                      index); +} + + +/** + * Offer serialized keys in a trait. + * + * @param index index number associate with the serial keys + *        on offer. + * @param keys serialized keys to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_exchange_keys +  (unsigned int index, +  const json_t *keys) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_EXCHANGE_KEYS, +    .ptr = (const json_t *) keys +  }; +  return ret; +} + + +/** + * Obtain wire details from @a cmd. + * + * @param cmd command to extract the wire details from. + * @param index index number associate with the wire details + *        on offer; usually zero, as one command sticks to + *        one bank account. + * @param[out] wire_details where to write the wire details. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_wire_details +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  const json_t **wire_details) +{ +  return cmd->traits (cmd->cls, +                      (const void **) wire_details, +                      TALER_TESTING_TRAIT_WIRE_DETAILS, +                      index); +} + + +/** + * Offer wire details in a trait. + * + * @param index index number associate with the wire details + *        on offer; usually zero, as one command sticks to + *        one bank account. + * @param wire_details wire details to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_wire_details +  (unsigned int index, +  const json_t *wire_details) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_WIRE_DETAILS, +    .ptr = (const json_t *) wire_details +  }; +  return ret; +} + + +/* end of testing_api_trait_json.c */ diff --git a/src/testing/testing_api_trait_merchant_key.c b/src/testing/testing_api_trait_merchant_key.c new file mode 100644 index 00000000..41b6b888 --- /dev/null +++ b/src/testing/testing_api_trait_merchant_key.c @@ -0,0 +1,127 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +  General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_trait_merchant_key.c + * @brief traits to offer peer's (private) keys + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_MERCHANT_PRIV "merchant-priv" +#define TALER_TESTING_TRAIT_MERCHANT_PUB "merchant-pub-pub" + +/** + * Obtain a private key from a "peer".  Used e.g. to obtain + * a merchant's priv to sign a /track request. + * + * @param cmd command that is offering the key. + * @param index (typically zero) which key to return if there + *        are multiple on offer. + * @param[out] priv set to the key coming from @a cmd. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_merchant_priv +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  const struct TALER_MerchantPrivateKeyP **priv) +{ +  return cmd->traits (cmd->cls, +                      (const void **) priv, +                      TALER_TESTING_TRAIT_MERCHANT_PRIV, +                      index); +} + + +/** + * Offer private key, typically done when CMD_1 needs it to + * sign a request. + * + * @param index (typically zero) which key to return if there are + *        multiple on offer. + * @param priv which object should be offered. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_merchant_priv (unsigned int index, +                                        const struct +                                        TALER_MerchantPrivateKeyP *priv) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_MERCHANT_PRIV, +    .ptr = (const void *) priv +  }; + +  return ret; +} + + +/** + * Obtain a public key from a "peer".  Used e.g. to obtain + * a merchant's public key to use backend's API. + * + * @param cmd command offering the key. + * @param index (typically zero) which key to return if there + *        are multiple on offer. + * @param[out] pub set to the key coming from @a cmd. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_merchant_pub +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  const struct TALER_MerchantPublicKeyP **pub) +{ +  return cmd->traits (cmd->cls, +                      (const void **) pub, +                      TALER_TESTING_TRAIT_MERCHANT_PUB, +                      index); +} + + +/** + * Offer public key. + * + * @param index (typically zero) which key to return if there + *        are multiple on offer.  NOTE: if one key is offered, it + *        is mandatory to set this as zero. + * @param pub which object should be returned. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_merchant_pub (unsigned int index, +                                       const struct +                                       TALER_MerchantPublicKeyP *pub) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_MERCHANT_PUB, +    .ptr = (const void *) pub +  }; + +  return ret; +} + + +/* end of testing_api_trait_merchant_key.c */ diff --git a/src/testing/testing_api_trait_number.c b/src/testing/testing_api_trait_number.c new file mode 100644 index 00000000..50ce6d8c --- /dev/null +++ b/src/testing/testing_api_trait_number.c @@ -0,0 +1,149 @@ +/* +  This file is part of TALER +  Copyright (C) 2018-2020 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +  General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_trait_number.c + * @brief traits to offer numbers + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_UINT "uint" +#define TALER_TESTING_TRAIT_UINT64 "uint-64" +#define TALER_TESTING_TRAIT_BANK_ROW "bank-transaction-row" + + +/** + * Obtain a number from @a cmd. + * + * @param cmd command to extract the number from. + * @param index the number's index number. + * @param[out] n set to the number coming from @a cmd. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_uint (const struct TALER_TESTING_Command *cmd, +                              unsigned int index, +                              const unsigned int **n) +{ +  return cmd->traits (cmd->cls, +                      (const void **) n, +                      TALER_TESTING_TRAIT_UINT, +                      index); +} + + +/** + * Offer a number. + * + * @param index the number's index number. + * @param n the number to offer. + * @return #GNUNET_OK on success. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_uint (unsigned int index, +                               const unsigned int *n) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_UINT, +    .ptr = (const void *) n +  }; +  return ret; +} + + +/** + * Obtain a "number" value from @a cmd, 64-bit version. + * + * @param cmd command to extract the number from. + * @param index the number's index number. + * @param[out] n set to the number coming from @a cmd. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_uint64 (const struct TALER_TESTING_Command *cmd, +                                unsigned int index, +                                const uint64_t **n) +{ +  return cmd->traits (cmd->cls, +                      (const void **) n, +                      TALER_TESTING_TRAIT_UINT64, +                      index); +} + + +/** + * Offer number trait, 64-bit version. + * + * @param index the number's index number. + * @param n number to offer. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_uint64 (unsigned int index, +                                 const uint64_t *n) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_UINT64, +    .ptr = (const void *) n +  }; +  return ret; +} + + +/** + * Obtain a bank transaction row value from @a cmd. + * + * @param cmd command to extract the number from. + * @param[out] row set to the number coming from @a cmd. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_bank_row (const struct TALER_TESTING_Command *cmd, +                                  const uint64_t **row) +{ +  return cmd->traits (cmd->cls, +                      (const void **) row, +                      TALER_TESTING_TRAIT_BANK_ROW, +                      0); +} + + +/** + * Offer bank transaction row trait. + * + * @param row number to offer. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_bank_row (const uint64_t *row) +{ +  struct TALER_TESTING_Trait ret = { +    .index = 0, +    .trait_name = TALER_TESTING_TRAIT_BANK_ROW, +    .ptr = (const void *) row +  }; +  return ret; +} + + +/* end of testing_api_trait_number.c */ diff --git a/src/testing/testing_api_trait_process.c b/src/testing/testing_api_trait_process.c new file mode 100644 index 00000000..3d2af31f --- /dev/null +++ b/src/testing/testing_api_trait_process.c @@ -0,0 +1,82 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as +  published by the Free Software Foundation; either version 3, +  or (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, +  but WITHOUT ANY WARRANTY; without even the implied warranty +  of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +  See the GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/testing_api_trait_process.c + * @brief trait offering process handles. + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_PROCESS "process" + + +/** + * Obtain location where a command stores a pointer to a process. + * + * @param cmd command to extract trait from. + * @param index which process to pick if @a cmd + *        has multiple on offer. + * @param[out] processp set to the address of the pointer to the + *        process. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_process +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  struct GNUNET_OS_Process ***processp) +{ +  return cmd->traits (cmd->cls, +                      (const void **) processp, +                      TALER_TESTING_TRAIT_PROCESS, +                      index); +} + + +/** + * Offer location where a command stores a pointer to a process. + * + * @param index offered location index number, in case there are + *        multiple on offer. + * @param processp process location to offer. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_process +  (unsigned int index, +  struct GNUNET_OS_Process **processp) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_PROCESS, +    .ptr = (const void *) processp +  }; + +  return ret; +} + + +/* end of testing_api_trait_process.c */ diff --git a/src/testing/testing_api_trait_reserve_priv.c b/src/testing/testing_api_trait_reserve_priv.c new file mode 100644 index 00000000..f4a4ef50 --- /dev/null +++ b/src/testing/testing_api_trait_reserve_priv.c @@ -0,0 +1,76 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as +  published by the Free Software Foundation; either version 3, or +  (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_trait_reserve_priv.c + * @brief implements reserve private key trait + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_RESERVE_PRIVATE_KEY \ +  "reserve-private-key" + +/** + * Obtain a reserve private key from a @a cmd. + * + * @param cmd command to extract the reserve priv from. + * @param index reserve priv's index number. + * @param[out] reserve_priv set to the reserve priv. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_reserve_priv +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  const struct TALER_ReservePrivateKeyP **reserve_priv) +{ +  return cmd->traits (cmd->cls, +                      (const void **) reserve_priv, +                      TALER_TESTING_TRAIT_RESERVE_PRIVATE_KEY, +                      index); +} + + +/** + * Offer a reserve private key. + * + * @param index reserve priv's index number. + * @param reserve_priv reserve private key to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_reserve_priv +  (unsigned int index, +  const struct TALER_ReservePrivateKeyP *reserve_priv) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_RESERVE_PRIVATE_KEY, +    .ptr = (const void *) reserve_priv +  }; +  return ret; +} + + +/* end of testing_api_trait_reserve_priv.c */ diff --git a/src/testing/testing_api_trait_reserve_pub.c b/src/testing/testing_api_trait_reserve_pub.c new file mode 100644 index 00000000..a158114b --- /dev/null +++ b/src/testing/testing_api_trait_reserve_pub.c @@ -0,0 +1,76 @@ +/* +  This file is part of TALER +  Copyright (C) 2018-2020 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify +  it under the terms of the GNU General Public License as +  published by the Free Software Foundation; either version 3, or +  (at your option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +  GNU General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_trait_reserve_pub.c + * @brief implements reserve public key trait + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_RESERVE_PUBLIC_KEY \ +  "reserve-public-key" + +/** + * Obtain a reserve public key from a @a cmd. + * + * @param cmd command to extract the reserve pub from. + * @param index reserve pub's index number. + * @param[out] reserve_pub set to the reserve pub. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_reserve_pub +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  const struct TALER_ReservePublicKeyP **reserve_pub) +{ +  return cmd->traits (cmd->cls, +                      (const void **) reserve_pub, +                      TALER_TESTING_TRAIT_RESERVE_PUBLIC_KEY, +                      index); +} + + +/** + * Offer a reserve public key. + * + * @param index reserve pub's index number. + * @param reserve_pub reserve public key to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_reserve_pub +  (unsigned int index, +  const struct TALER_ReservePublicKeyP *reserve_pub) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_RESERVE_PUBLIC_KEY, +    .ptr = (const void *) reserve_pub +  }; +  return ret; +} + + +/* end of testing_api_trait_reserve_pub.c */ diff --git a/src/testing/testing_api_trait_string.c b/src/testing/testing_api_trait_string.c new file mode 100644 index 00000000..38176011 --- /dev/null +++ b/src/testing/testing_api_trait_string.c @@ -0,0 +1,231 @@ +/* +  This file is part of TALER +  Copyright (C) 2018-2020 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +  General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ +/** + * @file testing/testing_api_trait_string.c + * @brief offers strings traits. + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +/** + * Some string. Avoid, use something more precise! + */ +#define TALER_TESTING_TRAIT_STRING "string" + +/** + * An HTTP-URL. + */ +#define TALER_TESTING_TRAIT_URL "url" + +/** + * A PAYTO-URL. + */ +#define TALER_TESTING_TRAIT_PAYTO "payto" + +/** + * String identifying an order. + */ +#define TALER_TESTING_TRAIT_ORDER_ID "order-id" + + +/** + * Obtain a string from @a cmd. + * + * @param cmd command to extract the subject from. + * @param index index number associated with the transfer + *        subject to offer. + * @param[out] s where to write the offered + *        string + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_string (const struct TALER_TESTING_Command *cmd, +                                unsigned int index, +                                const char **s) +{ +  return cmd->traits (cmd->cls, +                      (const void **) s, +                      TALER_TESTING_TRAIT_STRING, +                      index); +} + + +/** + * Offer string. + * + * @param index index number associated with the transfer + *        subject being offered. + * @param s transfer subject to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_string (unsigned int index, +                                 const char *s) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_STRING, +    .ptr = (const void *) s +  }; +  return ret; +} + + +/** + * Obtain a HTTP url from @a cmd. + * + * @param cmd command to extract the url from. + * @param index which url is to be picked, in case + *        multiple are offered. + * @param[out] url where to write the url. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_url (const struct TALER_TESTING_Command *cmd, +                             unsigned int index, +                             const char **url) +{ +  return cmd->traits (cmd->cls, +                      (const void **) url, +                      TALER_TESTING_TRAIT_URL, +                      index); +} + + +/** + * Offer HTTP url in a trait. + * + * @param index which url is to be picked, + *        in case multiple are offered. + * @param url the url to offer. + * + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_url (unsigned int index, +                              const char *url) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_URL, +    .ptr = (const void *) url +  }; + +  GNUNET_assert (0 != strncasecmp (url, +                                   "payto://", +                                   strlen ("payto://"))); + +  return ret; +} + + +/** + * Obtain a order id from @a cmd. + * + * @param cmd command to extract the order id from. + * @param index which order id is to be picked, in case + *        multiple are offered. + * @param[out] order_id where to write the order id. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_order_id (const struct TALER_TESTING_Command *cmd, +                                  unsigned int index, +                                  const char **order_id) +{ +  return cmd->traits (cmd->cls, +                      (const void **) order_id, +                      TALER_TESTING_TRAIT_ORDER_ID, +                      index); +} + + +/** + * Offer order id in a trait. + * + * @param index which order id is to be offered, + *        in case multiple are offered. + * @param order_id the order id to offer. + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_order_id (unsigned int index, +                                   const char *order_id) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_ORDER_ID, +    .ptr = (const void *) order_id +  }; +  return ret; +} + + +/** + * Obtain a PAYTO-url from @a cmd. + * + * @param cmd command to extract the url from. + * @param pt which url is to be picked, in case + *        multiple are offered. + * @param[out] url where to write the url. + * @return #GNUNET_OK on success. + */ +int +TALER_TESTING_get_trait_payto (const struct TALER_TESTING_Command *cmd, +                               enum TALER_TESTING_PaytoType pt, +                               const char **url) +{ +  return cmd->traits (cmd->cls, +                      (const void **) url, +                      TALER_TESTING_TRAIT_PAYTO, +                      (unsigned int) pt); +} + + +/** + * Offer a "payto" URL reference. + * + * @param pt which reference is to be offered, + *        in case multiple are offered. + * @param payto_uri the payto URI + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_payto (enum TALER_TESTING_PaytoType pt, +                                const char *payto_uri) +{ +  struct TALER_TESTING_Trait ret = { +    .index = (unsigned int) pt, +    .trait_name = TALER_TESTING_TRAIT_PAYTO, +    .ptr = (const void *) payto_uri, +  }; + +  GNUNET_assert (0 == strncasecmp (payto_uri, +                                   "payto://", +                                   strlen ("payto://"))); +  return ret; +} + + +/* end of testing_api_trait_string.c */ diff --git a/src/testing/testing_api_trait_time.c b/src/testing/testing_api_trait_time.c new file mode 100644 index 00000000..c77489bf --- /dev/null +++ b/src/testing/testing_api_trait_time.c @@ -0,0 +1,76 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +  General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/testing_api_trait_time.c + * @brief traits to offer time stamps. + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_TIME_ABS "time-abs" + +/** + * Obtain a absolute time from @a cmd. + * + * @param cmd command to extract trait from + * @param index which time stamp to pick if + *        @a cmd has multiple on offer. + * @param[out] time set to the wanted WTID. + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_get_trait_absolute_time +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  const struct GNUNET_TIME_Absolute **time) +{ +  return cmd->traits (cmd->cls, +                      (const void **) time, +                      TALER_TESTING_TRAIT_TIME_ABS, +                      index); +} + + +/** + * Offer a absolute time. + * + * @param index associate the object with this index + * @param time which object should be returned + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_absolute_time +  (unsigned int index, +  const struct GNUNET_TIME_Absolute *time) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_TIME_ABS, +    .ptr = (const void *) time +  }; +  return ret; +} + + +/* end of testing_api_trait_time.c */ diff --git a/src/testing/testing_api_trait_wtid.c b/src/testing/testing_api_trait_wtid.c new file mode 100644 index 00000000..5c7e7060 --- /dev/null +++ b/src/testing/testing_api_trait_wtid.c @@ -0,0 +1,76 @@ +/* +  This file is part of TALER +  Copyright (C) 2018 Taler Systems SA + +  TALER is free software; you can redistribute it and/or modify it +  under the terms of the GNU General Public License as published +  by the Free Software Foundation; either version 3, or (at your +  option) any later version. + +  TALER is distributed in the hope that it will be useful, but +  WITHOUT ANY WARRANTY; without even the implied warranty of +  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +  General Public License for more details. + +  You should have received a copy of the GNU General Public +  License along with TALER; see the file COPYING.  If not, see +  <http://www.gnu.org/licenses/> +*/ + +/** + * @file testing/testing_api_trait_number.c + * @brief traits to offer numbers + * @author Marcello Stanisci + */ +#include "platform.h" +#include "taler_json_lib.h" +#include <gnunet/gnunet_curl_lib.h> +#include "taler_signatures.h" +#include "taler_testing_lib.h" + +#define TALER_TESTING_TRAIT_WTID "wtid" + +/** + * Obtain a WTID value from @a cmd. + * + * @param cmd command to extract trait from + * @param index which WTID to pick if @a cmd has multiple on + *        offer + * @param[out] wtid set to the wanted WTID. + * @return #GNUNET_OK on success + */ +int +TALER_TESTING_get_trait_wtid +  (const struct TALER_TESTING_Command *cmd, +  unsigned int index, +  const struct TALER_WireTransferIdentifierRawP **wtid) +{ +  return cmd->traits (cmd->cls, +                      (const void **) wtid, +                      TALER_TESTING_TRAIT_WTID, +                      index); +} + + +/** + * Offer a WTID. + * + * @param index associate the object with this index + * @param wtid which object should be returned + * @return the trait. + */ +struct TALER_TESTING_Trait +TALER_TESTING_make_trait_wtid +  (unsigned int index, +  const struct TALER_WireTransferIdentifierRawP *wtid) +{ +  struct TALER_TESTING_Trait ret = { +    .index = index, +    .trait_name = TALER_TESTING_TRAIT_WTID, +    .ptr = (const void *) wtid +  }; +  return ret; +} + + +/* end of testing_api_trait_number.c */ diff --git a/src/testing/testing_api_traits.c b/src/testing/testing_api_traits.c new file mode 100644 index 00000000..6d623af7 --- /dev/null +++ b/src/testing/testing_api_traits.c @@ -0,0 +1,81 @@ +/* +  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_traits.c + * @brief loop for trait resolution + * @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" + + +/** + * End a trait array.  Usually, commands offer several traits, + * and put them in arrays. + */ +struct TALER_TESTING_Trait +TALER_TESTING_trait_end () +{ +  struct TALER_TESTING_Trait end = { +    .index = 0, +    .trait_name = NULL, +    .ptr = NULL +  }; + +  return end; +} + + +/** + * Pick the chosen trait from the traits array. + * + * @param traits the traits array. + * @param ret where to store the result. + * @param trait type of the trait to extract. + * @param index index number of the object to extract. + * @return #GNUNET_OK if no error occurred, #GNUNET_SYSERR otherwise. + */ +int +TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits, +                         const void **ret, +                         const char *trait, +                         unsigned int index) +{ +  for (unsigned int i = 0; NULL != traits[i].trait_name; i++) +  { +    if ( (0 == strcmp (trait, traits[i].trait_name)) && +         (index == traits[i].index) ) +    { +      *ret = (void *) traits[i].ptr; +      return GNUNET_OK; +    } +  } +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "Trait %s/%u not found.\n", +              trait, index); + +  return GNUNET_SYSERR; +} + + +/* end of testing_api_traits.c */  | 
