commit 5f6d203e072e430f5db0956ee42ef5f460ccc5dc
parent 9d6dc3cdc14b574ddff38224f6ad9fa8e0ec0a6e
Author: Christian Grothoff <christian@grothoff.org>
Date: Sat, 1 Aug 2026 00:33:19 +0200
start with KYC auditor test logic
Diffstat:
3 files changed, 911 insertions(+), 376 deletions(-)
diff --git a/src/auditor/generate-kyc-basedb.conf b/src/auditor/generate-kyc-basedb.conf
@@ -1,4 +1,99 @@
# This file is in the public domain.
+#
+# Configuration for the KYC/AML reference database used by test-kyc.sh.
+# It is the ordinary auditor reference configuration plus a KYC setup that
+# is deliberately easy to drive from a shell script: every rule that we
+# trigger resolves to a FORM measure, and a form can be satisfied with two
+# plain HTTP requests (GET /kyc-info/$TOKEN, POST /kyc-upload/$ID).
@INLINE@ generate-auditor-basedb.conf
-# FIXME: add options for KYC here!
-\ No newline at end of file
+[exchange]
+ENABLE_KYC = YES
+
+
+# ------------------------------------------------------------------
+# Boilerplate that every KYC configuration needs: the FREEZE measure
+# is the fallback whenever an AML program fails or a check expires,
+# and NONE is a program that imposes no new rules at all.
+# ------------------------------------------------------------------
+
+[KYC-MEASURE-FREEZE]
+CHECK_NAME = SKIP
+CONTEXT = {}
+PROGRAM = FREEZE
+
+[AML-PROGRAM-FREEZE]
+COMMAND = taler-exchange-helper-measure-freeze
+ENABLED = true
+DESCRIPTION = Freeze the account
+DESCRIPTION_I18N = {}
+FALLBACK = FREEZE
+
+[AML-PROGRAM-NONE]
+COMMAND = /bin/true
+ENABLED = true
+DESCRIPTION = Impose no new rules
+DESCRIPTION_I18N = {}
+FALLBACK = FREEZE
+
+
+# ------------------------------------------------------------------
+# C1/M1/P1: ask for a full name and a date of birth. Once the form
+# has been submitted, taler-exchange-helper-measure-test-form raises
+# every limit to TESTKUDOS:1000, which un-blocks the account.
+# ------------------------------------------------------------------
+
+[KYC-CHECK-C1]
+TYPE = FORM
+FORM_NAME = full_name_and_birthdate
+DESCRIPTION = Provide your full name and date of birth
+DESCRIPTION_I18N = {}
+OUTPUTS = FULL_NAME DATE_OF_BIRTH
+FALLBACK = FREEZE
+
+[KYC-MEASURE-M1]
+CHECK_NAME = C1
+CONTEXT = {}
+PROGRAM = P1
+
+[AML-PROGRAM-P1]
+COMMAND = taler-exchange-helper-measure-test-form
+ENABLED = true
+DESCRIPTION = Check FULL_NAME and DATE_OF_BIRTH
+DESCRIPTION_I18N = {}
+FALLBACK = FREEZE
+
+
+# ------------------------------------------------------------------
+# The rules. The AGGREGATE threshold sits below the TESTKUDOS:4 that
+# the reference wallet pays each merchant but above zero, which is
+# what makes the two interesting states reachable from a script: a
+# merchant may still accept the order (nothing has been aggregated
+# yet, so it is within its limit), the exchange accepts the coins,
+# and only the wire transfer is held back until measure M1 has been
+# passed. That held-back transfer is the KYC-induced lag the auditor
+# has to report.
+#
+# WITHDRAW has a threshold well above what the reference wallet ever
+# withdraws, so the rule is recorded in the configuration -- and thus
+# exercised by the exchange -- without blocking the interaction that
+# produces the rest of the reference database.
+# ------------------------------------------------------------------
+
+[KYC-RULE-AGGREGATE]
+OPERATION_TYPE = AGGREGATE
+ENABLED = YES
+EXPOSED = YES
+IS_AND_COMBINATOR = YES
+THRESHOLD = TESTKUDOS:3
+TIMEFRAME = 1d
+NEXT_MEASURES = M1
+
+[KYC-RULE-WITHDRAW]
+OPERATION_TYPE = WITHDRAW
+ENABLED = YES
+EXPOSED = YES
+IS_AND_COMBINATOR = YES
+THRESHOLD = TESTKUDOS:100
+TIMEFRAME = 1d
+NEXT_MEASURES = M1
diff --git a/src/auditor/meson.build b/src/auditor/meson.build
@@ -297,17 +297,22 @@ test(
is_parallel: false,
timeout: 1800,
)
-#test_kyc = configure_file(
-# input: 'test-kyc.sh',
-# output: 'test-kyc.sh',
-# copy: true,
-#)
-#test(
-# 'test-kyc',
-# test_kyc,
-# workdir: meson.current_build_dir(),
-# suite: ['auditor', 'installcheck'],
-#)
+# test-kyc audits a reference database produced by a KYC-enabled exchange
+# (generate-kyc-basedb.sh). See issues.txt for the KYC/AML checks the
+# auditor does not implement yet and that this test therefore leaves out.
+test_kyc = configure_file(
+ input: 'test-kyc.sh',
+ output: 'test-kyc.sh',
+ copy: true,
+)
+test(
+ 'test-kyc',
+ test_kyc,
+ workdir: meson.current_build_dir(),
+ suite: ['auditor', 'installcheck'],
+ is_parallel: false,
+ timeout: 1800,
+)
# test-revocation audits denomination revocation, which relies on recoup.
# The exchange's recoup handlers are currently compiled out (FIXME_9828,
# https://bugs.gnunet.org/view.php?id=9828), so the test detects the missing
@@ -347,6 +352,7 @@ EXTRA_DIST = [
'test-sync-out.conf',
'generate-auditor-basedb.sh',
'generate-auditor-basedb.conf',
+ 'generate-kyc-basedb.sh',
'generate-kyc-basedb.conf',
'generate-revoke-basedb.sh',
]
diff --git a/src/auditor/test-kyc.sh b/src/auditor/test-kyc.sh
@@ -1,7 +1,7 @@
-#!/bin/sh
+#!/bin/bash
#
# This file is part of TALER
-# Copyright (C) 2014-2023 Taler Systems SA
+# Copyright (C) 2014-2026 Taler Systems SA
#
# TALER is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
@@ -20,19 +20,33 @@
#
#
# Setup database which was generated from a perfectly normal
-# exchange-wallet interaction with KYC enabled and transactions
-# blocked due to KYC and run the auditor against it.
+# exchange-wallet-merchant interaction on a KYC-enabled exchange and run
+# the auditor against it.
#
-# Check that the auditor report is as expected.
+# The reference database (see generate-kyc-basedb.sh) contains two merchant
+# instances that were paid the same amount and both proved that they own
+# their bank account with a KYCAUTH wire transfer, but only one of them
+# then passed the KYC form. The exchange therefore wired the funds of the
+# first one out and is still holding the funds of the second one.
#
-# Requires 'jq' tool and Postgres superuser rights!
+# The auditor has almost no KYC-specific logic of its own -- see issues.txt
+# for what it could check and does not -- so this test has two jobs:
+#
+# 1. the KYC artefacts (KYCAUTH credits, legitimization measures and
+# outcomes, an aggregation the exchange lawfully withheld) must not
+# make the auditor invent findings, and the one KYC-specific balance it
+# does track, total_kycauth_in, must be exactly right;
+# 2. fault detection must still work on a KYC-enabled exchange, so the
+# later tests inject the same kinds of faults test-auditor.sh does and
+# require the auditor to flag them.
#
+# Requires 'jq' tool and Postgres superuser rights!
set -eu
#set -x
# Set of numbers for all the testcases.
# When adding new tests, increase the last number:
-ALL_TESTS=$(seq 0 1)
+ALL_TESTS=$(seq 0 7)
# $TESTS determines which tests we should run.
# This construction is used to make it easy to
@@ -46,22 +60,20 @@ ALL_TESTS=$(seq 0 1)
#
TESTS=${1:-$ALL_TESTS}
+export TALER_AUDITOR_TOKEN="secret-token:D4CST1Z6AHN3RT03M0T9NSTF2QGHTB5ZD2D3RYZB4HAWG8SX0JEFWBXCKXZHMB7Y3Z7KVFW0B3XPXD5BHCFP8EB0R6CNH2KAWDWVET0"
+export TALER_AUDITOR_SALT="64S36D1N6RVKGC9J6CT3ADHQ70RK4CSM6MV3EE1H68SK8D9P6WW32CHK6GTKCDSR64S36D1N6RVKGC9J6CT3ADHQ70RK4CSM6MV3EE0"
+
# Global variable to run the auditor processes under valgrind
# VALGRIND=valgrind
VALGRIND=""
-# Number of seconds to let libeuifn background
-# tasks apply a cycle of payment submission and
-# history request.
-LIBEUFIN_SETTLE_TIME=1
-
. setup.sh
# Cleanup exchange and libeufin between runs.
function cleanup()
{
- if test ! -z "${EPID:-}"
+ if [ -n "${EPID:-}" ]
then
echo -n "Stopping exchange $EPID..."
kill -TERM "$EPID"
@@ -69,23 +81,25 @@ function cleanup()
echo "DONE"
unset EPID
fi
- stop_libeufin
+ stop_libeufin &> /dev/null
}
# Cleanup to run whenever we exit
function exit_cleanup()
{
- echo "Running exit-cleanup"
- if test ! -z "${POSTGRES_PATH:-}"
+ jobs
+ if [ -n "${POSTGRES_PATH:-}" ]
then
- echo "Stopping Postgres at ${POSTGRES_PATH}"
+ echo -n "Stopping Postgres at ${POSTGRES_PATH} ..."
"${POSTGRES_PATH}/pg_ctl" \
-D "$TMPDIR" \
- -l /dev/null \
+ --log="${MY_TMP_DIR}/pg_ctl.log" \
stop \
- &> /dev/null \
+ &> ${MY_TMP_DIR}/pg_ctl.out \
|| true
+ echo "DONE"
fi
+ echo -n "Running exit-cleanup ..."
cleanup
for n in $(jobs -p)
do
@@ -99,29 +113,7 @@ function exit_cleanup()
trap exit_cleanup EXIT
-
-# Operations to run before the actual audit
-function pre_audit () {
- # Launch bank
- echo -n "Launching bank"
- launch_libeufin
- for n in $(seq 1 80)
- do
- echo -n "."
- sleep 0.1
- OK=1
- wget http://localhost:18082/ \
- -o /dev/null \
- -O /dev/null \
- >/dev/null \
- && break
- OK=0
- done
- if [ 1 != "$OK" ]
- then
- exit_skip "Failed to launch Sandbox"
- fi
- sleep "$LIBEUFIN_SETTLE_TIME"
+function await_bank () {
for n in $(seq 1 80)
do
echo -n "."
@@ -136,14 +128,27 @@ function pre_audit () {
done
if [ 1 != "$OK" ]
then
- exit_skip "Failed to launch Nexus"
+ exit_skip "Failed to launch libeufin-bank"
fi
+ }
+
+# Operations to run before the actual audit
+function pre_audit () {
+ # Launch bank
+ echo -n "Launching libeufin-bank"
+ export CONF
+ export MY_TMP_DIR
+ launch_libeufin
+ await_bank
echo " DONE"
- if test "${1:-no}" = "aggregator"
+
+ if [ "${1:-no}" = "aggregator" ]
then
+ # NO '-y' here: that is --kyc-off, which makes the aggregator
+ # wire funds out without consulting KYC at all and would defeat
+ # the point of this test.
echo -n "Running exchange aggregator ..."
taler-exchange-aggregator \
- -y \
-L "INFO" \
-t \
-c "$CONF" \
@@ -181,91 +186,140 @@ function audit_only () {
$VALGRIND taler-helper-auditor-aggregation \
-L DEBUG \
-c "$CONF" \
- -m "$MASTER_PUB" \
- > test-audit-aggregation.json \
- 2> "${MY_TMP_DIR}/test-audit-aggregation.log" \
- || exit_fail "aggregation audit failed"
+ -t \
+ > "${MY_TMP_DIR}/test-audit-aggregation.out" \
+ 2> "${MY_TMP_DIR}/test-audit-aggregation.err" \
+ || exit_fail "aggregation audit failed (see ${MY_TMP_DIR}/test-audit-aggregation.*)"
echo -n "."
$VALGRIND taler-helper-auditor-aggregation \
-L DEBUG \
-c "$CONF" \
- -m "$MASTER_PUB" \
- > test-audit-aggregation-inc.json \
- 2> "${MY_TMP_DIR}/test-audit-aggregation-inc.log" \
- || exit_fail "incremental aggregation audit failed"
+ -t \
+ > "${MY_TMP_DIR}/test-audit-aggregation-inc.out" \
+ 2> "${MY_TMP_DIR}/test-audit-aggregation-inc.err" \
+ || exit_fail "incremental aggregation audit failed (see ${MY_TMP_DIR}/test-audit-aggregation-inc.*)"
echo -n "."
$VALGRIND taler-helper-auditor-coins \
-L DEBUG \
-c "$CONF" \
- -m "$MASTER_PUB" \
- > test-audit-coins.json \
- 2> "${MY_TMP_DIR}/test-audit-coins.log" \
- || exit_fail "coin audit failed"
+ -t \
+ > "${MY_TMP_DIR}/test-audit-coins.out" \
+ 2> "${MY_TMP_DIR}/test-audit-coins.err" \
+ || exit_fail "coin audit failed (see ${MY_TMP_DIR}/test-audit-coins.*)"
echo -n "."
$VALGRIND taler-helper-auditor-coins \
-L DEBUG \
-c "$CONF" \
- -m "$MASTER_PUB" \
- > test-audit-coins-inc.json \
- 2> "${MY_TMP_DIR}/test-audit-coins-inc.log" \
- || exit_fail "incremental coin audit failed"
+ -t \
+ > "${MY_TMP_DIR}/test-audit-coins-inc.out" \
+ 2> "${MY_TMP_DIR}/test-audit-coins-inc.err" \
+ || exit_fail "incremental coin audit failed (see ${MY_TMP_DIR}/test-audit-coins-inc.*)"
echo -n "."
$VALGRIND taler-helper-auditor-deposits \
-L DEBUG \
-c "$CONF" \
- -m "$MASTER_PUB" \
- > test-audit-deposits.json \
- 2> "${MY_TMP_DIR}/test-audit-deposits.log" \
- || exit_fail "deposits audit failed"
+ -t \
+ > "${MY_TMP_DIR}/test-audit-deposits.out" \
+ 2> "${MY_TMP_DIR}/test-audit-deposits.err" \
+ || exit_fail "deposits audit failed (see ${MY_TMP_DIR}/test-audit-deposits.*)"
echo -n "."
$VALGRIND taler-helper-auditor-deposits \
-L DEBUG \
-c "$CONF" \
- -m "$MASTER_PUB" \
- > test-audit-deposits-inc.json \
- 2> "${MY_TMP_DIR}/test-audit-deposits-inc.log" \
- || exit_fail "incremental deposits audit failed"
+ -t \
+ > "${MY_TMP_DIR}/test-audit-deposits-inc.out" \
+ 2> "${MY_TMP_DIR}/test-audit-deposits-inc.err" \
+ || exit_fail "incremental deposits audit failed (see ${MY_TMP_DIR}/test-audit-deposits-inc.*)"
echo -n "."
$VALGRIND taler-helper-auditor-reserves \
-i \
-L DEBUG \
-c "$CONF" \
- -m "$MASTER_PUB" \
- > test-audit-reserves.json \
- 2> "${MY_TMP_DIR}/test-audit-reserves.log" \
- || exit_fail "reserves audit failed"
+ -t \
+ > "${MY_TMP_DIR}/test-audit-reserves.out" \
+ 2> "${MY_TMP_DIR}/test-audit-reserves.err" \
+ || exit_fail "reserves audit failed (see ${MY_TMP_DIR}/test-audit-reserves.*)"
echo -n "."
$VALGRIND taler-helper-auditor-reserves \
-i \
-L DEBUG \
-c "$CONF" \
- -m "$MASTER_PUB" \
- > test-audit-reserves-inc.json \
- 2> "${MY_TMP_DIR}/test-audit-reserves-inc.log" \
- || exit_fail "incremental reserves audit failed"
+ -t \
+ > "${MY_TMP_DIR}/test-audit-reserves-inc.out" \
+ 2> "${MY_TMP_DIR}/test-audit-reserves-inc.err" \
+ || exit_fail "incremental reserves audit failed (see ${MY_TMP_DIR}/test-audit-reserves-inc.*)"
echo -n "."
- rm -f "${MY_TMP_DIR}/test-wire-audit.log"
- thaw() {
- $VALGRIND taler-helper-auditor-wire \
- -i \
- -L DEBUG \
- -c "$CONF" \
- -m "$MASTER_PUB" \
- > test-audit-wire.json \
- 2>> "${MY_TMP_DIR}/test-wire-audit.log"
- }
- thaw || ( echo -e " FIRST CALL TO taler-helper-auditor-wire FAILED,\nRETRY AFTER TWO SECONDS..." | tee -a "${MY_TMP_DIR}/test-wire-audit.log"
- sleep 2
- thaw || exit_fail "wire audit failed" )
+ $VALGRIND taler-helper-auditor-wire-credit \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-wire-credit.out" \
+ 2> "${MY_TMP_DIR}/test-audit-wire-credit.err" \
+ || exit_fail "wire credit audit failed (see ${MY_TMP_DIR}/test-audit-wire-credit.*)"
+ echo -n "."
+ $VALGRIND taler-helper-auditor-wire-credit \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-wire-credit-inc.out" \
+ 2> "${MY_TMP_DIR}/test-audit-wire-credit-inc.err" \
+ || exit_fail "wire credit audit inc failed (see ${MY_TMP_DIR}/test-audit-wire-credit-inc.*)"
+ echo -n "."
+ $VALGRIND taler-helper-auditor-wire-debit \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-wire-debit.out" \
+ 2> "${MY_TMP_DIR}/test-audit-wire-debit.err" \
+ || exit_fail "wire debit audit failed (see ${MY_TMP_DIR}/test-audit-wire-debit.*)"
+ echo -n "."
+ $VALGRIND taler-helper-auditor-wire-debit \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-wire-debit-inc.out" \
+ 2> "${MY_TMP_DIR}/test-audit-wire-debit-inc.err" \
+ || exit_fail "wire debit audit inc failed (see ${MY_TMP_DIR}/test-audit-wire-debit-inc.*)"
+ echo -n "."
+ $VALGRIND taler-helper-auditor-purses \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-purses.out" \
+ 2> "${MY_TMP_DIR}/test-audit-purses.err" \
+ || exit_fail "audit purses failed"
echo -n "."
- $VALGRIND taler-helper-auditor-wire \
+ $VALGRIND taler-helper-auditor-purses \
-i \
-L DEBUG \
-c "$CONF" \
- -m "$MASTER_PUB" \
- > test-audit-wire-inc.json \
- 2> "${MY_TMP_DIR}/test-wire-audit-inc.log" \
- || exit_fail "wire audit inc failed"
+ -t \
+ > "${MY_TMP_DIR}/test-audit-purses-inc.out" \
+ 2> "${MY_TMP_DIR}/test-audit-purses-inc.err" \
+ || exit_fail "audit purses inc failed"
+ echo -n "."
+ $VALGRIND taler-helper-auditor-transfer \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-transfer.out" \
+ 2> "${MY_TMP_DIR}/test-audit-transfer.err" \
+ || exit_fail "audit transfer failed"
+ echo -n "."
+ $VALGRIND taler-helper-auditor-transfer \
+ -i \
+ -L DEBUG \
+ -c "$CONF" \
+ -t \
+ > "${MY_TMP_DIR}/test-audit-transfer-inc.out" \
+ 2> "${MY_TMP_DIR}/test-audit-transfer-inc.err" \
+ || exit_fail "audit transfer inc failed"
echo -n "."
echo " DONE"
@@ -279,7 +333,6 @@ function post_audit () {
-g \
|| exit_fail "exchange DB GC failed"
cleanup
- echo " DONE"
}
@@ -290,73 +343,28 @@ function post_audit () {
# Pass "drain" as $2 to run a drain operation as well.
function run_audit () {
pre_audit "${1:-no}"
- if test "${2:-no}" = "drain"
- then
- echo -n "Starting exchange..."
- taler-exchange-httpd \
- -c "${CONF}" \
- -L INFO \
- 2> "${MY_TMP_DIR}/exchange-httpd-drain.err" &
- EPID=$!
-
- # Wait for all services to be available
- for n in $(seq 1 50)
- do
- echo -n "."
- sleep 0.1
- OK=0
- # exchange
- wget "http://localhost:8081/seed" \
- -o /dev/null \
- -O /dev/null \
- >/dev/null \
- || continue
- OK=1
- break
- done
- echo "... DONE."
- export CONF
-
- echo -n "Running taler-exchange-offline drain "
-
- taler-exchange-offline \
- -L DEBUG \
- -c "${CONF}" \
- drain TESTKUDOS:0.1 \
- exchange-account-1 payto://iban/DE360679?receiver-name=Exchange+Drain \
- upload \
- 2> "${MY_TMP_DIR}/taler-exchange-offline-drain.log" \
- || exit_fail "offline draining failed"
- kill -TERM "$EPID"
- wait "$EPID" || true
- unset EPID
- echo -n "Running taler-exchange-drain ..."
- printf "\n" | taler-exchange-drain \
- -L DEBUG \
- -c "$CONF" \
- 2> "${MY_TMP_DIR}/taler-exchange-drain.log" \
- || exit_fail "FAIL"
- echo " DONE"
- fi
- echo -n "Running taler-exchange-transfer ..."
- taler-exchange-transfer \
- -L INFO \
- -t \
- -c "$CONF" \
- 2> "${MY_TMP_DIR}/drain-transfer.log" \
- || exit_fail "FAIL"
- echo " DONE"
-
audit_only
post_audit
}
+function stop_auditor_httpd() {
+ if [ -n "${APID:-}" ]
+ then
+ echo -n "Stopping auditor $APID..."
+ kill -TERM "$APID"
+ wait "$APID" || true
+ echo "DONE"
+ unset APID
+ fi
+}
+
+
# Do a full reload of the (original) database
function full_reload()
{
echo -n "Doing full reload of the database (loading ${BASEDB}.sql into $DB at ${PGHOST:-})... "
- dropdb "$DB" 2> /dev/null || true
+ dropdb -f "$DB" &>> ${MY_TMP_DIR}/drop.log || true
createdb -T template0 "$DB" \
|| exit_skip "could not create database $DB (at ${PGHOST:-})"
# Import pre-generated database, -q(ietly) using single (-1) transaction
@@ -364,238 +372,626 @@ function full_reload()
-q \
-1 \
-f "${BASEDB}.sql" \
- > /dev/null \
+ &>> ${MY_TMP_DIR}/postgresql-reload.log \
|| exit_skip "Failed to load database $DB from ${BASEDB}.sql"
echo "DONE"
# Technically, this call shouldn't be needed as libeufin should already be stopped here...
stop_libeufin
+ stop_auditor_httpd
}
+function run_auditor_httpd() {
+ echo -n "Starting auditor..."
+ $VALGRIND taler-auditor-httpd \
+ -c "${CONF}" \
+ -L INFO \
+ 2> "${MY_TMP_DIR}/auditor-httpd.err" &
+ APID=$!
+
+ # Wait for auditor service to be available
+ for n in $(seq 1 50)
+ do
+ echo -n "."
+ sleep 0.2
+ OK=0
+ # auditor
+ wget "http://localhost:8083/config" \
+ -o /dev/null \
+ -O /dev/null \
+ >/dev/null \
+ || continue
+ OK=1
+ break
+ done
+ echo "... DONE."
+}
-function test_0() {
-
- echo "===========0: normal run with aggregator==========="
- run_audit aggregator
- echo "Checking output"
- # if an emergency was detected, that is a bug and we should fail
- echo -n "Test for emergencies... "
- jq -e .emergencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency detected in ordinary run" || echo PASS
- echo -n "Test for deposit confirmation emergencies... "
- jq -e .deposit_confirmation_inconsistencies[0] < test-audit-deposits.json > /dev/null && exit_fail "Unexpected deposit confirmation inconsistency detected" || echo PASS
- echo -n "Test for emergencies by count... "
- jq -e .emergencies_by_count[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected emergency by count detected in ordinary run" || echo PASS
-
- echo -n "Test for wire inconsistencies... "
- jq -e .wire_out_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire out inconsistency detected in ordinary run"
- jq -e .reserve_in_amount_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected reserve in inconsistency detected in ordinary run"
- jq -e .misattribution_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected misattribution inconsistency detected in ordinary run"
- jq -e .row_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected row inconsistency detected in ordinary run"
- jq -e .denomination_key_validity_withdraw_inconsistencies[0] < test-audit-reserves.json > /dev/null && exit_fail "Unexpected denomination key withdraw inconsistency detected in ordinary run"
- jq -e .row_minor_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected minor row inconsistency detected in ordinary run"
- jq -e .lag_details[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected lag detected in ordinary run"
- jq -e .wire_format_inconsistencies[0] < test-audit-wire.json > /dev/null && exit_fail "Unexpected wire format inconsistencies detected in ordinary run"
-
-
- # TODO: check operation balances are correct (once we have all transaction types and wallet is deterministic)
- # TODO: check revenue summaries are correct (once we have all transaction types and wallet is deterministic)
-
- echo PASS
- LOSS=$(jq -r .total_bad_sig_loss < test-audit-aggregation.json)
- if [ "$LOSS" != "TESTKUDOS:0" ]
- then
- exit_fail "Wrong total bad sig loss from aggregation, got unexpected loss of $LOSS"
- fi
- LOSS=$(jq -r .irregular_loss < test-audit-coins.json)
- if [ "$LOSS" != "TESTKUDOS:0" ]
+function check_auditor_running() {
+ ARUNSTATUS=$(curl -Is http://localhost:8083/config | head -1)
+ if [ -n "${ARUNSTATUS:-}" ]
then
- exit_fail "Wrong total bad sig loss from coins, got unexpected loss of $LOSS"
- fi
- LOSS=$(jq -r .total_bad_sig_loss < test-audit-reserves.json)
- if [ "$LOSS" != "TESTKUDOS:0" ]
- then
- exit_fail "Wrong total bad sig loss from reserves, got unexpected loss of $LOSS"
- fi
+ echo "Auditor running"
+ else
+ echo "Auditor not running, starting it"
+ run_auditor_httpd
+ fi
+ unset ARUNSTATUS
+}
- echo -n "Test for wire amounts... "
- WIRED=$(jq -r .total_wire_in_delta_plus < test-audit-wire.json)
- if [ "$WIRED" != "TESTKUDOS:0" ]
- then
- exit_fail "Expected total wire delta plus wrong, got $WIRED"
- fi
- WIRED=$(jq -r .total_wire_in_delta_minus < test-audit-wire.json)
- if [ "$WIRED" != "TESTKUDOS:0" ]
- then
- exit_fail "Expected total wire delta minus wrong, got $WIRED"
- fi
- WIRED=$(jq -r .total_wire_out_delta_plus < test-audit-wire.json)
- if [ "$WIRED" != "TESTKUDOS:0" ]
+function call_endpoint() {
+ if [ -n "${2+x}" ]
then
- exit_fail "Expected total wire delta plus wrong, got $WIRED"
+ curl -s -H "Accept: application/json" -H "Authorization: Bearer ${TALER_AUDITOR_TOKEN}" -o "${MY_TMP_DIR}/${2}.json" "localhost:8083/monitoring/${1}?limit=50&balance_key=${2}"
+ echo "endpoint ${1} called (with balance_key)... "
+ else
+ curl -s -H "Accept: application/json" -H "Authorization: Bearer ${TALER_AUDITOR_TOKEN}" -o "${MY_TMP_DIR}/${1}.json" "localhost:8083/monitoring/${1}?limit=50"
+ echo "endpoint ${1} called... "
fi
- WIRED=$(jq -r .total_wire_out_delta_minus < test-audit-wire.json)
- if [ "$WIRED" != "TESTKUDOS:0" ]
+}
+
+
+function check_balance() {
+ call_endpoint "balances" "$1"
+ BAL=$(jq -r .balances[0].balance_value < "${MY_TMP_DIR}/${1}.json")
+ if [ "$BAL" != "$2" ]
then
- exit_fail "Expected total wire delta minus wrong, got $WIRED"
+ exit_fail "$3 (got $BAL, wanted $2)"
fi
- WIRED=$(jq -r .total_misattribution_in < test-audit-wire.json)
- if [ "$WIRED" != "TESTKUDOS:0" ]
+ echo "PASS"
+}
+
+
+function check_not_balance() {
+ call_endpoint "balances" "$1"
+ BAL=$(jq -r .balances[0].balance_value < "${MY_TMP_DIR}/${1}.json")
+ if [ "$BAL" = "$2" ]
then
- exit_fail "Expected total misattribution in wrong, got $WIRED"
+ exit_fail "$3 (got $BAL, wanted NOT $2)"
fi
echo "PASS"
+}
- echo -n "Checking for unexpected arithmetic differences "
- LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-aggregation.json)
- if [ "$LOSS" != "TESTKUDOS:0" ]
+
+function check_report() {
+ call_endpoint "$1"
+ NAME=$(echo "$1" | tr '-' '_')
+ # shellcheck disable=SC2086
+ VAL=$(jq -r .\"${NAME}\"[0].\"$2\" < "${MY_TMP_DIR}/${1}.json")
+ if [ "$VAL" != "$3" ]
then
- exit_fail "Wrong arithmetic delta from aggregations, got unexpected plus of $LOSS"
+ exit_fail "$1::$2 (got $VAL, wanted $3)"
fi
- LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-aggregation.json)
- if [ "$LOSS" != "TESTKUDOS:0" ]
+ echo "PASS"
+}
+
+# Check that at least one entry of report $1 has field $2 set to $3.
+# Unlike check_report, this does not depend on the order in which the
+# auditor inserted its findings: one fault can legitimately be reported
+# against several operations (a coin with a bad denomination signature is
+# flagged once per operation that used it).
+function check_report_any() {
+ call_endpoint "$1"
+ NAME=$(echo "$1" | tr '-' '_')
+ # shellcheck disable=SC2086
+ jq -e --arg want "$3" "any(.\"${NAME}\"[]; .\"$2\" == \$want)" \
+ < "${MY_TMP_DIR}/${1}.json" \
+ > /dev/null \
+ || exit_fail "$1::$2 (no entry with value $3)"
+ echo "PASS"
+}
+
+# Check that report $1 has at least one entry, without pinning down any of
+# its fields. Useful when the injected fault is certain to be found but
+# the exact amounts depend on how much of a reserve happened to be left.
+function check_some_report() {
+ call_endpoint "$1"
+ NAME=$(echo "$1" | tr '-' '_')
+ # shellcheck disable=SC2086
+ jq -e .\"${NAME}\"[0] \
+ < "${MY_TMP_DIR}/${1}.json" \
+ > /dev/null \
+ || exit_fail "Wanted a report for $1, but got none"
+ echo "PASS"
+}
+
+function check_no_report() {
+ call_endpoint "$1"
+ NAME=$(echo "$1" | tr '-' '_')
+ # shellcheck disable=SC2086
+ jq -e .\"${NAME}\"[0] \
+ < "${MY_TMP_DIR}/${1}.json" \
+ > /dev/null \
+ && exit_fail "Wanted empty report for $1, but got incidents"
+ echo "PASS"
+}
+
+function check_report_neg() {
+ call_endpoint "$1"
+ NAME=$(echo "$1" | tr '-' '_')
+ # shellcheck disable=SC2086
+ VAL=$(jq -r .\"${NAME}\"[0].\"$2\" < "${MY_TMP_DIR}/${1}.json")
+ if [ "$VAL" == "$3" ]
then
- exit_fail "Wrong arithmetic delta from aggregation, got unexpected minus of $LOSS"
+ exit_fail "$1::$2 (got $VAL, wanted $3)"
fi
- LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-coins.json)
- if [ "$LOSS" != "TESTKUDOS:0" ]
+ echo "PASS"
+}
+
+function check_row() {
+ call_endpoint "$1"
+ NAME=$(echo "$1" | tr '-' '_')
+ if [ -n "${3+x}" ]
then
- exit_fail "Wrong arithmetic delta from coins, got unexpected plus of $LOSS"
+ RID="$2"
+ WANT="$3"
+ else
+ RID="row_id"
+ WANT="$2"
fi
- LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-coins.json)
- if [ "$LOSS" != "TESTKUDOS:0" ]
+ # shellcheck disable=SC2086
+ ROW=$(jq -r .\"${NAME}\"[0].\"${RID}\" < "${MY_TMP_DIR}/${1}.json")
+ if [ "$ROW" != "$WANT" ]
then
- exit_fail "Wrong arithmetic delta from coins, got unexpected minus of $LOSS"
+ exit_fail "Row ${1} wrong (got ${ROW}, wanted ${WANT})"
fi
- LOSS=$(jq -r .total_arithmetic_delta_plus < test-audit-reserves.json)
- if [ "$LOSS" != "TESTKUDOS:0" ]
+ echo "PASS"
+}
+
+
+
+# Amount the exchange wired to the merchant that passed KYC.
+CLEARED_WIRED="TESTKUDOS:8.02"
+# Amount the exchange aggregated for the merchant that did not pass KYC
+# and then parked in aggregation_transient instead of wiring it out.
+WITHHELD="TESTKUDOS:8.03"
+# Two KYCAUTH wire transfers of TESTKUDOS:0.1, one per merchant instance.
+KYCAUTH_IN="TESTKUDOS:0.2"
+
+
+# Assert that all of the auditor's loss, delta and irregularity balances
+# are zero, i.e. that nothing about this database looks wrong to it.
+function check_all_clean() {
+ echo -n "Test for emergencies... "
+ check_no_report "emergency"
+ echo -n "Test for emergencies by count... "
+ check_no_report "emergency-by-count"
+ echo -n "Test for deposit confirmation problems... "
+ check_no_report "deposit-confirmation"
+ echo -n "Test for denomination key validity problems... "
+ check_no_report "denomination-key-validity-withdraw-inconsistency"
+ echo -n "Test for denominations without signatures... "
+ check_no_report "denominations-without-sigs"
+ echo -n "Test for wire out inconsistencies... "
+ check_no_report "wire-out-inconsistency"
+ echo -n "Test for reserve in inconsistencies... "
+ check_no_report "reserve-in-inconsistency"
+ echo -n "Test for misattribution inconsistencies... "
+ check_no_report "misattribution-in-inconsistency"
+ echo -n "Test for row inconsistencies... "
+ check_no_report "row-inconsistency"
+ echo -n "Test for minor row inconsistencies... "
+ check_no_report "row-minor-inconsistencies"
+ echo -n "Test for wire format inconsistencies... "
+ check_no_report "wire-format-inconsistency"
+ echo -n "Test for arithmetic inconsistencies... "
+ check_no_report "amount-arithmetic-inconsistency"
+ echo -n "Test for coin inconsistencies... "
+ check_no_report "coin-inconsistency"
+ echo -n "Test for bad signature losses... "
+ check_no_report "bad-sig-losses"
+ echo -n "Test for fee time inconsistencies... "
+ check_no_report "fee-time-inconsistency"
+ echo -n "Test for purses not closed... "
+ check_no_report "purse-not-closed-inconsistencies"
+ echo -n "Test for reserves not closed... "
+ check_no_report "reserve-not-closed-inconsistency"
+ echo -n "Test for insufficient reserve balances... "
+ check_no_report "reserve-balance-insufficient-inconsistency"
+ echo -n "Test for wrong reserve balance summaries... "
+ check_no_report "reserve-balance-summary-wrong-inconsistency"
+ echo -n "Test for closure lags... "
+ check_no_report "closure-lags"
+
+ # Just to test the endpoint and for logging ...
+ call_endpoint "balances"
+
+ echo -n "Testing aggregation bad sig loss balance... "
+ check_balance \
+ "aggregation_total_bad_sig_loss" \
+ "TESTKUDOS:0" \
+ "Wrong total bad sig loss from aggregation"
+ echo -n "Testing coin irregular loss balance... "
+ check_balance \
+ "coin_irregular_loss" \
+ "TESTKUDOS:0" \
+ "Wrong total irregular loss from coins"
+ echo -n "Testing reserves bad sig loss balance... "
+ check_balance \
+ "reserves_total_bad_sig_loss" \
+ "TESTKUDOS:0" \
+ "Wrong total bad sig loss from reserves"
+ echo -n "Testing purse bad sig loss balance... "
+ check_balance \
+ "purse_total_bad_sig_loss" \
+ "TESTKUDOS:0" \
+ "Wrong total bad sig loss from purses"
+
+ echo -n "Test for bad incoming delta plus... "
+ check_balance \
+ "total_bad_amount_in_plus" \
+ "TESTKUDOS:0" \
+ "Expected total wire in delta plus wrong"
+ echo -n "Test for bad incoming delta minus... "
+ check_balance \
+ "total_bad_amount_in_minus" \
+ "TESTKUDOS:0" \
+ "Expected total wire in delta minus wrong"
+ echo -n "Test for bad outgoing delta plus... "
+ check_balance \
+ "total_bad_amount_out_plus" \
+ "TESTKUDOS:0" \
+ "Expected total wire out delta plus wrong"
+ echo -n "Test for bad outgoing delta minus... "
+ check_balance \
+ "total_bad_amount_out_minus" \
+ "TESTKUDOS:0" \
+ "Expected total wire out delta minus wrong"
+ echo -n "Test for misattribution amounts... "
+ check_balance \
+ "total_misattribution_in" \
+ "TESTKUDOS:0" \
+ "Expected total misattribution in wrong"
+
+ echo -n "Test for aggregation wire out delta plus... "
+ check_balance \
+ "aggregation_total_wire_out_delta_plus" \
+ "TESTKUDOS:0" \
+ "Expected total wire out delta plus wrong"
+ echo -n "Test for aggregation wire out delta minus... "
+ check_balance \
+ "aggregation_total_wire_out_delta_minus" \
+ "TESTKUDOS:0" \
+ "Expected total wire out delta minus wrong"
+
+ echo -n "Checking for unexpected aggregation delta plus... "
+ check_balance \
+ "aggregation_total_arithmetic_delta_plus" \
+ "TESTKUDOS:0" \
+ "Wrong arithmetic delta plus from aggregations"
+ echo -n "Checking for unexpected aggregation delta minus... "
+ check_balance \
+ "aggregation_total_arithmetic_delta_minus" \
+ "TESTKUDOS:0" \
+ "Wrong arithmetic delta minus from aggregations"
+ echo -n "Checking for unexpected coin delta plus... "
+ check_balance \
+ "coins_total_arithmetic_delta_plus" \
+ "TESTKUDOS:0" \
+ "Wrong arithmetic delta plus from coins"
+ echo -n "Checking for unexpected coin delta minus... "
+ check_balance \
+ "coins_total_arithmetic_delta_minus" \
+ "TESTKUDOS:0" \
+ "Wrong arithmetic delta minus from coins"
+ echo -n "Checking for unexpected reserves delta plus... "
+ check_balance \
+ "reserves_total_arithmetic_delta_plus" \
+ "TESTKUDOS:0" \
+ "Wrong arithmetic delta plus from reserves"
+ echo -n "Checking for unexpected reserves delta minus... "
+ check_balance \
+ "reserves_total_arithmetic_delta_minus" \
+ "TESTKUDOS:0" \
+ "Wrong arithmetic delta minus from reserves"
+}
+
+
+# The KYC-specific assertions: how much arrived by KYCAUTH, how much left
+# the exchange, and how much it is sitting on.
+function check_kyc_state() {
+ echo -n "Checking the KYCAUTH credits were accounted for... "
+ check_balance \
+ "total_kycauth_in" \
+ "$KYCAUTH_IN" \
+ "Wrong total for KYCAUTH wire transfers"
+
+ echo -n "Checking that only the cleared merchant was paid... "
+ check_balance \
+ "total_wire_out" \
+ "$CLEARED_WIRED" \
+ "Wrong total wired out"
+
+ # The exchange aggregated the blocked merchant's coins, wrote the
+ # aggregation_tracking rows and then parked the payout in
+ # aggregation_transient rather than executing it. Confirm from the
+ # exchange's own database that the money really is still there, so
+ # that the auditor-side assertion below is about the auditor and not
+ # about the reference database having drifted.
+ echo -n "Checking that the exchange withheld the blocked payout... "
+ TRANSIENT=$(psql -Aqt "$DB" \
+ -c "SELECT COUNT(*) FROM exchange.aggregation_transient;")
+ if [ "$TRANSIENT" != "1" ]
then
- exit_fail "Wrong arithmetic delta from reserves, got unexpected plus of $LOSS"
+ exit_fail "Expected one withheld aggregation, got ${TRANSIENT}"
fi
- LOSS=$(jq -r .total_arithmetic_delta_minus < test-audit-reserves.json)
- if [ "$LOSS" != "TESTKUDOS:0" ]
+ WIRED=$(psql -Aqt "$DB" -c "SELECT COUNT(*) FROM exchange.wire_out;")
+ if [ "$WIRED" != "1" ]
then
- exit_fail "Wrong arithmetic delta from reserves, got unexpected minus of $LOSS"
+ exit_fail "Expected exactly one wire transfer, got ${WIRED}"
fi
-
- jq -e .amount_arithmetic_inconsistencies[0] < test-audit-aggregation.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from aggregations detected in ordinary run"
- jq -e .amount_arithmetic_inconsistencies[0] < test-audit-coins.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from coins detected in ordinary run"
- jq -e .amount_arithmetic_inconsistencies[0] < test-audit-reserves.json > /dev/null && exit_fail "Unexpected arithmetic inconsistencies from reserves detected in ordinary run"
echo "PASS"
- echo -n "Checking for unexpected wire out differences "
- jq -e .wire_out_inconsistencies[0] < test-audit-aggregation.json > /dev/null && exit_fail "Unexpected wire out inconsistencies detected in ordinary run"
- echo "PASS"
+ # ... and the auditor does not notice. See issues.txt, issue 9: the
+ # withheld deposits carry aggregation_tracking rows, which
+ # taler-helper-auditor-transfer.c accepts as proof that they were
+ # paid, so no lag is reported for the WITHHELD amount. This
+ # assertion pins down the current behaviour; when the auditor learns
+ # to require a wire_out row it will fail here and should then be
+ # changed to expect the withheld amount instead.
+ echo -n "Checking the (unimplemented) view of withheld funds... "
+ check_balance \
+ "total_amount_lag" \
+ "TESTKUDOS:0" \
+ "Withheld funds are now visible to the auditor: update this test and issues.txt (issue 9), expected ${WITHHELD}"
+ echo -n "Test for pending deposits... "
+ check_no_report "pending-deposits"
+ echo -n "Test for early aggregations... "
+ check_no_report "early-aggregation"
+}
+
+
+# The KYC artefacts must not make the auditor invent findings.
+function test_0() {
+
+ echo "===========0: normal run with aggregator==========="
+ run_audit aggregator
+ check_auditor_running
+
+ echo "Checking output"
+ check_all_clean
+ check_kyc_state
# cannot easily undo aggregator, hence full reload
full_reload
-
+ cleanup
}
-# Run without aggregator, hence auditor should detect wire
-# transfer lag!
+# Same, but without letting the aggregator run first. The aggregator
+# cannot lawfully do anything more than it already did when the reference
+# database was generated, so the auditor's view must be identical.
function test_1() {
echo "===========1: normal run==========="
run_audit
+ check_auditor_running
echo "Checking output"
- # if an emergency was detected, that is a bug and we should fail
- echo -n "Test for emergencies... "
- jq -e .emergencies[0] \
- < test-audit-coins.json \
- > /dev/null \
- && exit_fail "Unexpected emergency detected in ordinary run";
- echo "PASS"
- echo -n "Test for emergencies by count... "
- jq -e .emergencies_by_count[0] \
- < test-audit-coins.json \
- > /dev/null \
- && exit_fail "Unexpected emergency by count detected in ordinary run"
- echo "PASS"
+ check_all_clean
+ check_kyc_state
- echo -n "Test for wire inconsistencies... "
- jq -e .wire_out_amount_inconsistencies[0] \
- < test-audit-wire.json \
- > /dev/null \
- && exit_fail "Unexpected wire out inconsistency detected in ordinary run"
- jq -e .reserve_in_amount_inconsistencies[0] \
- < test-audit-wire.json \
- > /dev/null \
- && exit_fail "Unexpected reserve in inconsistency detected in ordinary run"
- jq -e .misattribution_inconsistencies[0] \
- < test-audit-wire.json \
- > /dev/null \
- && exit_fail "Unexpected misattribution inconsistency detected in ordinary run"
- jq -e .row_inconsistencies[0] \
- < test-audit-wire.json \
- > /dev/null \
- && exit_fail "Unexpected row inconsistency detected in ordinary run"
- jq -e .row_minor_inconsistencies[0] \
- < test-audit-wire.json \
- > /dev/null \
- && exit_fail "Unexpected minor row inconsistency detected in ordinary run"
- jq -e .wire_format_inconsistencies[0] \
- < test-audit-wire.json \
- > /dev/null \
- && exit_fail "Unexpected wire format inconsistencies detected in ordinary run"
+ # Database was unmodified, no need to undo
+ echo "OK"
+}
- # TODO: check operation balances are correct (once we have all transaction types and wallet is deterministic)
- # TODO: check revenue summaries are correct (once we have all transaction types and wallet is deterministic)
- echo "PASS"
+# A KYCAUTH credit that the bank does not agree is a KYCAUTH credit.
+function test_2() {
- echo -n "Check for lag detection... "
+ echo "===========2: KYCAUTH credit reclassified by the bank==========="
+ # Modifying the bank's books needs libeufin to be restarted, it does
+ # not re-read them.
+ pre_audit
+ stop_libeufin
+ echo -n "Modifying database: "
+ # libeufin records for every credit to the exchange whether it was a
+ # reserve top-up or an account authorisation. Turn one of the two
+ # KYCAUTH credits into a reserve credit: the KYCAUTH total the auditor
+ # tracks must drop by that amount, and because the 32 bytes of KYCAUTH
+ # metadata are not a reserve public key the exchange knows, the credit
+ # now also looks like an incoming transfer the exchange never booked.
+ echo "UPDATE libeufin_bank.taler_exchange_incoming SET type='reserve' WHERE exchange_incoming_id=(SELECT MIN(exchange_incoming_id) FROM libeufin_bank.taler_exchange_incoming WHERE type='kyc')" \
+ | psql -Aqt "$DB"
+ echo "DONE"
+ launch_libeufin
+ await_bank
- # Check wire transfer lag reported (no aggregator!)
- # NOTE: This test is EXPECTED to fail for ~1h after
- # re-generating the test database as we do not
- # report lag of less than 1h (see GRACE_PERIOD in
- # taler-helper-auditor-wire.c)
- jq -e .lag_details[0] \
- < test-audit-wire.json \
- > /dev/null \
- || exit_fail "Lag not detected in run without aggregator"
+ audit_only
+ check_auditor_running
+
+ echo -n "Checking that the KYCAUTH total dropped... "
+ check_balance \
+ "total_kycauth_in" \
+ "TESTKUDOS:0.1" \
+ "Wrong total for KYCAUTH wire transfers"
+ echo -n "Checking that the unbooked credit was flagged... "
+ check_report_any \
+ "reserve-in-inconsistency" \
+ "diagnostic" "unknown to exchange"
+ echo -n "Checking that it was counted as an excess credit... "
+ check_balance \
+ "total_bad_amount_in_plus" \
+ "TESTKUDOS:0.1" \
+ "Wrong total_bad_amount_in_plus"
- LAG=$(jq -r .total_amount_lag < test-audit-wire.json)
- if [ "$LAG" = "TESTKUDOS:0" ]
- then
- exit_fail "Expected total lag to be non-zero"
- fi
- echo "PASS"
+ post_audit
+ full_reload
+ cleanup
+}
- echo -n "Test for wire amounts... "
- WIRED=$(jq -r .total_wire_in_delta_plus < test-audit-wire.json)
- if [ "$WIRED" != "TESTKUDOS:0" ]
- then
- exit_fail "Expected total wire delta plus wrong, got $WIRED"
- fi
- WIRED=$(jq -r .total_wire_in_delta_minus < test-audit-wire.json)
- if [ "$WIRED" != "TESTKUDOS:0" ]
- then
- exit_fail "Expected total wire delta minus wrong, got $WIRED"
- fi
- WIRED=$(jq -r .total_wire_out_delta_plus < test-audit-wire.json)
- if [ "$WIRED" != "TESTKUDOS:0" ]
- then
- exit_fail "Expected total wire delta plus wrong, got $WIRED"
- fi
- WIRED=$(jq -r .total_wire_out_delta_minus < test-audit-wire.json)
- if [ "$WIRED" != "TESTKUDOS:0" ]
- then
- exit_fail "Expected total wire delta minus wrong, got $WIRED"
- fi
- WIRED=$(jq -r .total_misattribution_in < test-audit-wire.json)
- if [ "$WIRED" != "TESTKUDOS:0" ]
- then
- exit_fail "Expected total misattribution in wrong, got $WIRED"
- fi
- # Database was unmodified, no need to undo
- echo "OK"
+# The rest of the tests inject the same kinds of faults test-auditor.sh
+# injects, to show that KYC does not stop the auditor from finding them.
+
+
+# Change the amount of an incoming wire transfer as recorded by the
+# exchange.
+function test_3() {
+
+ echo "===========3: reserves_in inconsistency==========="
+ echo -n "Modifying database: "
+ echo "UPDATE exchange.reserves_in SET credit.val=5 WHERE reserve_in_serial_id=1" \
+ | psql -Aqt "$DB"
+ echo "DONE"
+
+ run_audit
+ check_auditor_running
+
+ echo -n "Testing inconsistency detection... "
+ check_report \
+ "reserve-in-inconsistency" \
+ "row_id" 1
+ echo -n "Testing amount wired... "
+ check_report \
+ "reserve-in-inconsistency" \
+ "amount_wired" "TESTKUDOS:10"
+ echo -n "Testing amount expected... "
+ check_report \
+ "reserve-in-inconsistency" \
+ "amount_exchange_expected" "TESTKUDOS:5"
+ echo -n "Checking wire credit balance plus... "
+ check_balance \
+ "total_bad_amount_in_plus" \
+ "TESTKUDOS:5" \
+ "Expected total_bad_amount_in_plus wrong"
+
+ full_reload
+ cleanup
}
+# Test where denom_sig in the known_coins table is wrong (=> bad signature)
+function test_4() {
+
+ echo "===========4: known_coins signature wrong==========="
+ OLD_ROW=$(echo "SELECT known_coin_id FROM exchange.known_coins LIMIT 1;" | psql "$DB" -Aqt)
+ COIN_PUB=$(echo "SELECT coin_pub FROM exchange.known_coins WHERE known_coin_id=$OLD_ROW;" | psql "$DB" -Aqt)
+# shellcheck disable=SC2028
+ echo "UPDATE exchange.known_coins SET denom_sig='\x0000000100000000287369672d76616c200a2028727361200a2020287320233542383731423743393036444643303442424430453039353246413642464132463537303139374131313437353746324632323332394644443146324643333445393939413336363430334233413133324444464239413833353833464536354442374335434445304441453035374438363336434541423834463843323843344446304144363030343430413038353435363039373833434431333239393736423642433437313041324632414132414435413833303432434346314139464635394244434346374436323238344143354544364131373739463430353032323241373838423837363535453434423145443831364244353638303232413123290a2020290a20290b' WHERE coin_pub='$COIN_PUB'" \
+ | psql -Aqt "$DB"
+
+ run_audit
+ check_auditor_running
+
+ echo -n "Checking bad-signature-loss detected... "
+ check_report_neg \
+ "bad-sig-losses" \
+ "loss" "TESTKUDOS:0"
+ echo -n "Checking bad-signature-loss balance update... "
+ check_not_balance \
+ "coin_irregular_loss" \
+ "TESTKUDOS:0" \
+ "Wrong total bad sig loss"
+
+ full_reload
+ cleanup
+}
+
+
+# Test where the amount the bank actually wired differs from what the
+# exchange claims.
+function test_5() {
+
+ echo "===========5: incorrect wire_out amount==========="
+ pre_audit
+ stop_libeufin
+ echo -n "Modifying database: "
+ echo "UPDATE libeufin_bank.bank_account_transactions SET amount=(50,0) WHERE debtor_name='Exchange Company';" \
+ | psql -Aqt "$DB"
+ echo "DONE"
+ launch_libeufin
+ await_bank
+
+ audit_only
+ check_auditor_running
+
+ echo -n "Testing wire-out-inconsistency expected... "
+ check_report \
+ "wire-out-inconsistency" \
+ "expected" \
+ "$CLEARED_WIRED"
+ echo -n "Testing wire-out-inconsistency claimed... "
+ check_report \
+ "wire-out-inconsistency" \
+ "claimed" \
+ "TESTKUDOS:50"
+ echo -n "Testing bad_amount_out_plus balance reporting... "
+ check_not_balance \
+ "total_bad_amount_out_plus" \
+ "TESTKUDOS:0" \
+ "reported total_bad_amount_out_plus wrong"
+
+ post_audit
+ full_reload
+ cleanup
+}
+
+
+# Test where the exchange claims to have received more than the bank sent,
+# which also throws its reserve balance summary off.
+function test_6() {
+
+ echo "===========6: reserve balance summary wrong==========="
+ echo -n "Modifying database: "
+ echo "UPDATE exchange.reserves_in SET credit.val=15 WHERE reserve_in_serial_id=1" \
+ | psql -Aqt "$DB"
+ echo "DONE"
+
+ run_audit
+ check_auditor_running
+
+ echo -n "Checking reserve balance summary inconsistency detected... "
+ check_some_report \
+ "reserve-balance-summary-wrong-inconsistency"
+ echo -n "Testing amount the exchange expected... "
+ check_report \
+ "reserve-in-inconsistency" \
+ "amount_exchange_expected" "TESTKUDOS:15"
+ echo -n "Checking wire credit balance minus... "
+ check_balance \
+ "total_bad_amount_in_minus" \
+ "TESTKUDOS:5" \
+ "Wrong total_bad_amount_in_minus"
+
+ full_reload
+ cleanup
+}
+
+
+# Test where the exchange forgot a deposit that a merchant can prove.
+function test_7() {
+
+ echo "===========7: deposit wire target malformed==========="
+ SERIAL=$(echo "SELECT batch_deposit_serial_id FROM exchange.coin_deposits WHERE (amount_with_fee).val=3 ORDER BY batch_deposit_serial_id LIMIT 1" | psql "$DB" -Aqt)
+# shellcheck disable=SC2028
+ echo "INSERT INTO exchange.wire_targets (payto_uri, wire_target_h_payto) VALUES ('payto://x-taler-bank/localhost/testuser-xxlargtp', '\x1e8f31936b3cee8f8afd3aac9e38b5db42d45b721ffc4eb1e5b9ddaf1565660b');" \
+ | psql "$DB" -Aqt
+# shellcheck disable=SC2028
+ echo "UPDATE exchange.batch_deposits SET wire_target_h_payto='\x1e8f31936b3cee8f8afd3aac9e38b5db42d45b721ffc4eb1e5b9ddaf1565660b' WHERE batch_deposit_serial_id=${SERIAL};" \
+ | psql -Aqt "$DB"
+
+ run_audit
+ check_auditor_running
+
+ echo -n "Checking correct operation of loss reported... "
+ check_report \
+ "bad-sig-losses" \
+ "operation" "deposit"
+ echo -n "Checking correct problem row ID reported... "
+ check_report \
+ "bad-sig-losses" \
+ "problem_row_id" "$SERIAL"
+ echo -n "Checking irregular coin loss... "
+ check_not_balance \
+ "coin_irregular_loss" \
+ "TESTKUDOS:0" \
+ "Wrong total irregular coin loss"
+
+ full_reload
+ cleanup
+}
+
# *************** Main test loop starts here **************
@@ -606,6 +1002,7 @@ function check_with_database()
{
BASEDB="$1"
CONF="$1.conf"
+ export CONF
echo "Running test suite with database $BASEDB using configuration $CONF"
MASTER_PRIV_FILE="${BASEDB}.mpriv"
taler-exchange-config \
@@ -614,9 +1011,6 @@ function check_with_database()
-s exchange-offline \
-o MASTER_PRIV_FILE \
-V "${MASTER_PRIV_FILE}"
- MASTER_PUB=$(gnunet-ecc -p "$MASTER_PRIV_FILE")
-
- echo "MASTER PUB is ${MASTER_PUB} using file ${MASTER_PRIV_FILE}"
# Load database
full_reload
@@ -635,23 +1029,24 @@ function check_with_database()
# dropdb $DB
}
-# If this script is not run as root, create the temporary
-# storage space for postgres.
-# Sets PGHOST accordingly
+# When the script is not run as root, setup a temporary directory for the
+# postgres database.
+# Sets PGHOST accordingly to the freshly created socket.
function perform_initdb() {
# Available directly in path?
INITDB_BIN=$(command -v initdb) || true
if [[ -n "$INITDB_BIN" ]]; then
echo " FOUND (in path) at $INITDB_BIN"
else
- HAVE_INITDB=$(find /usr -name "initdb" | head -1 2> /dev/null | grep postgres) \
+ HAVE_INITDB=$(find /usr -name "initdb" 2> /dev/null \
+ | head -1 2> /dev/null \
+ | grep postgres) \
|| exit_skip " MISSING"
echo " FOUND at $(dirname "$HAVE_INITDB")"
INITDB_BIN=$(echo "$HAVE_INITDB" | grep bin/initdb | grep postgres | sort -n | tail -n1)
fi
POSTGRES_PATH=$(dirname "$INITDB_BIN")
- echo "Using $MY_TMP_DIR for logging and temporary data"
TMPDIR="$MY_TMP_DIR/postgres"
mkdir -p "$TMPDIR"
echo -n "Setting up Postgres DB at $TMPDIR ..."
@@ -660,11 +1055,29 @@ function perform_initdb() {
--auth=trust \
-D "${TMPDIR}" \
> "${MY_TMP_DIR}/postgres-dbinit.log" \
- 2> "${MY_TMP_DIR}/postgres-dbinit.err"
+ 2> "${MY_TMP_DIR}/postgres-dbinit.err" \
+ || {
+ echo "FAILED!"
+ echo "Last entries in ${MY_TMP_DIR}/postgres-dbinit.err:"
+ tail "${MY_TMP_DIR}/postgres-dbinit.err"
+ exit 1
+ }
echo "DONE"
+
+ # Once we move to PG16, we can use:
+ # --set listen_addresses='' \
+ # --set fsync=off \
+ # --set max_wal_senders=0 \
+ # --set synchronous_commit=off \
+ # --set wal_level=minimal \
+ # --set unix_socket_directories="${TMPDIR}/sockets" \
+
+
SOCKETDIR="${TMPDIR}/sockets"
mkdir "${SOCKETDIR}"
+
echo -n "Launching Postgres service"
+
cat - >> "$TMPDIR/postgresql.conf" <<EOF
unix_socket_directories='${TMPDIR}/sockets'
fsync=off
@@ -673,24 +1086,23 @@ synchronous_commit=off
wal_level=minimal
listen_addresses=''
EOF
+
grep -v host \
< "$TMPDIR/pg_hba.conf" \
> "$TMPDIR/pg_hba.conf.new"
mv "$TMPDIR/pg_hba.conf.new" "$TMPDIR/pg_hba.conf"
"${POSTGRES_PATH}/pg_ctl" \
- -D "$TMPDIR" \
- -l /dev/null \
- start \
- > "${MY_TMP_DIR}/postgres-start.log" \
- 2> "${MY_TMP_DIR}/postgres-start.err"
+ -D "$TMPDIR" \
+ -l "${MY_TMP_DIR}/postgres.log" \
+ start \
+ > "${MY_TMP_DIR}/postgres-start.log" \
+ 2> "${MY_TMP_DIR}/postgres-start.err"
echo " DONE"
PGHOST="$TMPDIR/sockets"
export PGHOST
-
}
-
# *************** Main logic starts here **************
# ####### Setup globals ######
@@ -700,38 +1112,61 @@ export DB="auditor-basedb"
# test required commands exist
echo "Testing for jq"
jq -h > /dev/null || exit_skip "jq required"
+echo "Testing for taler-merchant-config"
+taler-merchant-config -h > /dev/null || exit_skip "taler-merchant-config required"
+echo "Testing for taler-merchant-httpd"
+taler-merchant-httpd -h > /dev/null || exit_skip "taler-merchant-httpd required"
echo "Testing for faketime"
faketime -h > /dev/null || exit_skip "faketime required"
# NOTE: really check for all three libeufin commands?
-echo "Testing for libeufin-bank"
-libeufin-bank --help >/dev/null 2> /dev/null </dev/null || exit_skip "libeufin-bank required"
+echo "Testing for libeufin"
+libeufin-bank --help >/dev/null 2> /dev/null </dev/null || exit_skip "libeufin required"
echo "Testing for taler-wallet-cli"
taler-wallet-cli -h >/dev/null </dev/null 2>/dev/null || exit_skip "taler-wallet-cli required"
-MY_TMP_DIR=$(mktemp -d /tmp/taler-auditor-basedbXXXXXX)
echo -n "Testing for Postgres"
+
+MY_TMP_DIR=$(mktemp -d /tmp/taler-auditor-basedbXXXXXX)
+echo "Using $MY_TMP_DIR for logging and temporary data"
+
+# If run as root, simply use the running postgres instance.
+# Otherwise create a temporary storage space for postgres.
[ $(id -u) == 0 ] || perform_initdb
MYDIR="${MY_TMP_DIR}/basedb"
mkdir -p "${MYDIR}"
-echo "Generating fresh database at $MYDIR"
-if faketime -f '-1 d' ./generate-auditor-basedb.sh \
- -c generate-kyc-basedb.conf \
- -d "$MYDIR/$DB"
+
+if [ -z ${REUSE_BASEDB_DIR+x} ]
then
- echo -n "Reset 'auditor-basedb' database at ${PGHOST:-} ..."
- dropdb "auditor-basedb" >/dev/null 2>/dev/null || true
- createdb "auditor-basedb" || exit_skip "Could not create database '$BASEDB' at ${PGHOST:-}"
- echo " DONE"
- check_with_database "$MYDIR/$DB"
- if [ "$fail" != "0" ]
+ echo "Generating fresh database at $MYDIR"
+
+ if faketime -f '-1 d' ./generate-kyc-basedb.sh -d "$MYDIR/$DB"
then
- exit "$fail"
+ echo -n "Reset 'auditor-basedb' database at ${PGHOST:-} ..."
+ dropdb --if-exists "auditor-basedb" > /dev/null 2> /dev/null || true
+ createdb "auditor-basedb" || exit_skip "Could not create database '$BASEDB' at ${PGHOST:-}"
+ echo " DONE"
+ else
+ echo "Generation failed"
+ exit 1
fi
+ echo "To reuse this database in the future, use:"
+ echo "export REUSE_BASEDB_DIR=$MY_TMP_DIR"
else
- echo "Generation failed"
- exit 1
+ echo "Reusing existing database from ${REUSE_BASEDB_DIR}"
+ cp -r "${REUSE_BASEDB_DIR}/basedb"/* "${MYDIR}/"
+fi
+
+check_with_database "$MYDIR/$DB"
+if [ "$fail" != "0" ]
+then
+ exit "$fail"
+fi
+
+if [ -z "${REUSE_BASEDB_DIR+x}" ]
+then
+ echo "Run 'export REUSE_BASEDB_DIR=${MY_TMP_DIR}' to re-run tests against the same database"
fi
exit 0