From f70b3a2afa6323a17896f7a8ee40c4712c5bf2c2 Mon Sep 17 00:00:00 2001 From: eljamm Date: Tue, 23 Jul 2024 18:20:41 +0100 Subject: [PATCH] tests/taler: init basic test Test build and services start, but libeufin-bank fails when trying to access the database to change the admin's password. We need to execute the command as the libeufin-bank user. tests/taler: add master private key tests/taler: rewrite `register_bank_account` to Nix tests/taler: rename libeufin node to bank tests/taler: use xtaler wire_type instead of iban tests/taler: remove redundant data from conf files tests/taler: enable exchange account tests/taler: remove unused talerConfig tests/taler: add client node and attempt a withdrawal tests/taler: systemd_run optional user and group args tests/taler: refactor and make a withdrawal tests/taler: refactor tasks into subtests tests/taler: properly read and test balance tests/taler: refactor commands and add comments nixos/taler: rename private key tests/taler: enable nexus service in bank node tests/taler: nexus fake incoming payment test tests/taler: use correct path for nexus client keys tests/taler: add merchant node tests/taler: merchant register instance tests/taler: init pay for order merchant tests/taler: fix payto uri tests/taler: withdraw smaller amount This makes the test faster tests/taler: verify balance tests/nixos: debugging merchant payment, cleanup tests/taler: fix libeufin command, use curl to register accounts tests/taler: add basic online test tests/taler: move nodes into separate directory tests/taler: fix insufficient balance error Turns out that the exchange wire fees need to be set up (even if they're 0) in order for the CLI wallet to deposit coins into the merchant's bank account. tests/taler: improve node importing, port forwarding tests/taler: import scripts from a separate file tests/taler: move tests into a sub-directory tests/taler: manually start services, cleanup This results in less overhead and conflict since components will not try to prematurely connect to the ones that haven't finished their set up. tests/taler: remove online test This was used to debug the insufficient balance problem, but it's not really that useful by itself. tests/taler: add nexus keys tests/taler: use bank initalAccounts option taler/tests: use initialAccount tests/taler: make nexus work tests/taler: don't run nexus test if there is no internet tests/taler: use openFirewall, remove manual package install fix(test): evaluation errors fix(test): create nexus role by enabling createLocalDatabase --- nixos/tests/all-tests.nix | 1 + nixos/tests/taler/common/nodes.nix | 200 +++++++++++++ nixos/tests/taler/common/scripts.nix | 107 +++++++ nixos/tests/taler/conf/bank-ebics-keys.json | 1 + nixos/tests/taler/conf/client-ebics-keys.json | 1 + nixos/tests/taler/conf/exchange-account.json | 16 ++ nixos/tests/taler/conf/private.key | 1 + nixos/tests/taler/conf/taler-accounts.conf | 10 + .../tests/taler/conf/taler-denominations.conf | 95 ++++++ nixos/tests/taler/default.nix | 7 + nixos/tests/taler/tests/basic.nix | 270 ++++++++++++++++++ 11 files changed, 709 insertions(+) create mode 100644 nixos/tests/taler/common/nodes.nix create mode 100644 nixos/tests/taler/common/scripts.nix create mode 100644 nixos/tests/taler/conf/bank-ebics-keys.json create mode 100644 nixos/tests/taler/conf/client-ebics-keys.json create mode 100644 nixos/tests/taler/conf/exchange-account.json create mode 100644 nixos/tests/taler/conf/private.key create mode 100644 nixos/tests/taler/conf/taler-accounts.conf create mode 100644 nixos/tests/taler/conf/taler-denominations.conf create mode 100644 nixos/tests/taler/default.nix create mode 100644 nixos/tests/taler/tests/basic.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index be546f2acc3aa..25c1e8bfad2ad 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1064,6 +1064,7 @@ in { systemd-userdbd = handleTest ./systemd-userdbd.nix {}; systemd-homed = handleTest ./systemd-homed.nix {}; systemtap = handleTest ./systemtap.nix {}; + taler = handleTest ./taler {}; tandoor-recipes = handleTest ./tandoor-recipes.nix {}; tandoor-recipes-script-name = handleTest ./tandoor-recipes-script-name.nix {}; tang = handleTest ./tang.nix {}; diff --git a/nixos/tests/taler/common/nodes.nix b/nixos/tests/taler/common/nodes.nix new file mode 100644 index 0000000000000..3e8b81ed5179d --- /dev/null +++ b/nixos/tests/taler/common/nodes.nix @@ -0,0 +1,200 @@ +{ lib, ... }: +let + # Forward SSH and WebUI ports to host machine + # + # Connect with: ssh root@localhost -p + # Access WebUI from: http://localhost: + # + # NOTE: This is only accessible from an interactive test, for example: + # $ eval $(nix-build -A nixosTests.taler.basic.driver)/bin/nixos-test-driver + mkNode = + { + sshPort ? 0, + webuiPort ? 0, + nodeSettings ? { }, + }: + lib.recursiveUpdate { + services.openssh = { + enable = true; + settings = { + PermitRootLogin = "yes"; + PermitEmptyPasswords = "yes"; + }; + }; + security.pam.services.sshd.allowNullPassword = true; + virtualisation.forwardPorts = + (lib.optionals (sshPort != 0) [ + { + from = "host"; + host.port = sshPort; + guest.port = 22; + } + ]) + ++ (lib.optionals (webuiPort != 0) [ + { + from = "host"; + host.port = webuiPort; + guest.port = webuiPort; + } + ]); + } nodeSettings; +in +rec { + CURRENCY = "KUDOS"; + FIAT_CURRENCY = "CHF"; + + nodes = { + exchange = + { config, lib, ... }: + mkNode { + sshPort = 1111; + webuiPort = 8081; + + nodeSettings = { + services.taler = { + settings = { + taler.CURRENCY = CURRENCY; + }; + includes = [ ../conf/taler-accounts.conf ]; + exchange = { + enable = true; + debug = true; + openFirewall = true; + denominationConfig = lib.readFile ../conf/taler-denominations.conf; + settings = { + exchange = { + MASTER_PUBLIC_KEY = "2TQSTPFZBC2MC4E52NHPA050YXYG02VC3AB50QESM6JX1QJEYVQ0"; + BASE_URL = "http://exchange:8081/"; + }; + exchange-offline = { + MASTER_PRIV_FILE = "${../conf/private.key}"; + }; + }; + }; + }; + }; + }; + + bank = + { config, ... }: + mkNode { + sshPort = 2222; + webuiPort = 8082; + + nodeSettings = { + services.libeufin.bank = { + enable = true; + debug = true; + + openFirewall = true; + createLocalDatabase = true; + + initialAccounts = [ + { + username = "exchange"; + password = "exchange"; + name = "Exchange"; + } + ]; + + settings = { + libeufin-bank = { + WIRE_TYPE = "x-taler-bank"; + # WIRE_TYPE = "iban"; + X_TALER_BANK_PAYTO_HOSTNAME = "bank:8082"; + # IBAN_PAYTO_BIC = "SANDBOXX"; + BASE_URL = "bank:8082"; + + # Allow creating new accounts + ALLOW_REGISTRATION = "yes"; + + # A registration bonus makes withdrawals easier since the + # bank account balance is not empty + REGISTRATION_BONUS_ENABLED = "yes"; + REGISTRATION_BONUS = "${CURRENCY}:100"; + + DEFAULT_DEBT_LIMIT = "${CURRENCY}:500"; + + # NOTE: The exchange's bank account must be initialised before + # the main bank service starts, else it doesn't work. + # The `services.libeufin.bank.initialAccounts` option can be used to do this. + ALLOW_CONVERSION = "yes"; + ALLOW_EDIT_CASHOUT_PAYTO_URI = "yes"; + + SUGGESTED_WITHDRAWAL_EXCHANGE = "http://exchange:8081/"; + + inherit CURRENCY FIAT_CURRENCY; + }; + }; + }; + + services.libeufin.nexus = { + enable = true; + debug = true; + + openFirewall = true; + createLocalDatabase = true; + + settings = { + # https://docs.taler.net/libeufin/setup-ebics-at-postfinance.html + nexus-ebics = { + # == Mandatory == + CURRENCY = FIAT_CURRENCY; + # Bank + HOST_BASE_URL = "https://isotest.postfinance.ch/ebicsweb/ebicsweb"; + BANK_DIALECT = "postfinance"; + # EBICS IDs + HOST_ID = "PFEBICS"; + USER_ID = "PFC00639"; + PARTNER_ID = "PFC00639"; + # Account information + IBAN = "CH4740123RW4167362694"; + BIC = "BIC"; + NAME = "nixosTest nixosTest"; + + # == Optional == + CLIENT_PRIVATE_KEYS_FILE = "${../conf/client-ebics-keys.json}"; + BANK_PUBLIC_KEYS_FILE = "${../conf/bank-ebics-keys.json}"; + }; + }; + }; + }; + }; + + merchant = + { config, ... }: + mkNode { + sshPort = 3333; + webuiPort = 8083; + + nodeSettings = { + services.taler = { + settings = { + taler.CURRENCY = CURRENCY; + }; + merchant = { + enable = true; + debug = true; + openFirewall = true; + settings.merchant-exchange-test = { + EXCHANGE_BASE_URL = "http://exchange:8081/"; + MASTER_KEY = "2TQSTPFZBC2MC4E52NHPA050YXYG02VC3AB50QESM6JX1QJEYVQ0"; + inherit CURRENCY; + }; + }; + }; + }; + }; + + client = + { pkgs, ... }: + mkNode { + sshPort = 4444; + + nodeSettings = { + environment.systemPackages = [ pkgs.taler-wallet-core ]; + }; + }; + }; + +} diff --git a/nixos/tests/taler/common/scripts.nix b/nixos/tests/taler/common/scripts.nix new file mode 100644 index 0000000000000..3860ae229070a --- /dev/null +++ b/nixos/tests/taler/common/scripts.nix @@ -0,0 +1,107 @@ +{ + lib, + pkgs, + nodes, + ... +}: + +let + cfgNodes = pkgs.callPackage ./nodes.nix { inherit lib; }; + bankConfig = nodes.bank.config.environment.etc."libeufin/libeufin.conf".source; + + inherit (cfgNodes) CURRENCY FIAT_CURRENCY; +in +{ + commonScripts = + # python + '' + def succeed(machine, commands): + """A more convenient `machine.succeed` that supports multi-line inputs""" + flattened_commands = [c.replace("\n", "") for c in commands] # flatten multi-line + return machine.succeed(" ".join(flattened_commands)) + + + def systemd_run(machine, cmd, user="nobody", group="nobody"): + """Execute command as a systemd DynamicUser""" + machine.log(f"Executing command (via systemd-run): \"{cmd}\"") + + (status, out) = machine.execute( " ".join([ + "systemd-run", + "--service-type=exec", + "--quiet", + "--wait", + "-E PATH=\"$PATH\"", + "-p StandardOutput=journal", + "-p StandardError=journal", + "-p DynamicUser=yes", + f"-p Group={group}" if group != "nobody" else "", + f"-p User={user}" if user != "nobody" else "", + f"$SHELL -c '{cmd}'" + ]) ) + + if status != 0: + raise Exception(f"systemd_run failed (status {status})") + + machine.log("systemd-run finished successfully") + + + def register_bank_account(username, password, name, is_exchange=False): + """Register Libeufin bank account for the x-taler-bank wire method""" + return systemd_run(bank, " ".join([ + 'libeufin-bank', + 'create-account', + '-c ${bankConfig}', + f'--username {username}', + f'--password {password}', + f'--name {name}', + f'--payto_uri="payto://x-taler-bank/bank:8082/{username}?receiver-name={name}"', + '--exchange' if (is_exchange or username.lower()=="exchange") else ' ' + ]), + user="libeufin-bank") + + + def wallet_cli(command): + """Wrapper for the Taler CLI wallet""" + return client.succeed( + "taler-wallet-cli " + "--no-throttle " # don't do any request throttling + + command + ) + + + def verify_balance(balanceWanted: str): + """Compare Taler CLI wallet balance with expected amount""" + balance = wallet_cli("balance --json") + try: + balanceGot = json.loads(balance)["balances"][0]["available"] + except: + balanceGot = "${CURRENCY}:0" + + # Compare balance with expected value + if balanceGot != balanceWanted: + client.fail(f'echo Wanted balance: "{balanceWanted}", got: "{balanceGot}"') + else: + client.succeed(f"echo Withdraw successfully made. New balance: {balanceWanted}") + + + def verify_conversion(regionalWanted: str): + """Compare converted Libeufin Nexus funds with expected regional currency""" + # Get transaction details + response = json.loads( + succeed(bank, [ + "curl -sSfL", + # TODO: get exchange from config? + "-u exchange:exchange", + "http://bank:8082/accounts/exchange/transactions" + ]) + ) + amount = response["transactions"][0]["amount"].split(":") # CURRENCY:VALUE + currencyGot, regionalGot = amount + + # Check conversion (1:1 ratio) + if (regionalGot != regionalWanted) or (currencyGot != "${CURRENCY}"): + client.fail(f'echo Wanted "${CURRENCY}:{regionalWanted}", got: "{currencyGot}:{regionalGot}"') + else: + client.succeed(f'echo Conversion successfully made: "${FIAT_CURRENCY}:{regionalWanted}" -> "{currencyGot}:{regionalGot}"') + ''; +} diff --git a/nixos/tests/taler/conf/bank-ebics-keys.json b/nixos/tests/taler/conf/bank-ebics-keys.json new file mode 100644 index 0000000000000..e3ebadb6ba5b8 --- /dev/null +++ b/nixos/tests/taler/conf/bank-ebics-keys.json @@ -0,0 +1 @@ +{"bank_encryption_public_key":"621028HG1M30JAM6923FE381040GA003G80GY01GG80GM0M2040G1EACATA11EF5SVKNBNBYF1S3WSKQ2A2R9VZ7RW2HRX00293JPZ7VQ780RFRVYTQKKDDNJAQGBH4659GT9QYBMJCG1RKZEH1WDJ0GAAY7B7NBMW6FWXCKFYRMZQME0WBGZ1AAMY2VBQ5XAFV8216EFNF2EPG6M5ZGHG9RG6EGED56TK9JESQ02Q7AAVBRAAARVBN9NHCN64KQ3SRRHYXB8RWRK4TSSC93XG8RWMQH4ZDJSBYDCEXFY6G3AWTZ0EZNCJJAYB98T4GNFWZMN81AVYCQHXT1APX81AXCAYNK7J9XETF5CN1J1WV0BVA2BYG4VAMAW123REPN67JF1TNWPTADBMHS17N2V1GFYT8JRWX4TGM2996NXTEPMA8C2CDDE0CRY2A6HT8C5H2D6C62YGRSCF820C0G008","bank_authentication_public_key":"621028HG1M30JAM6923FE381040GA003G80GY01GG80GM0M2040G1EACATA11EF5SVKNBNBYF1S3WSKQ2A2R9VZ7RW2HRX00293JPZ7VQ780RFRVYTQKKDDNJAQGBH4659GT9QYBMJCG1RKZEH1WDJ0GAAY7B7NBMW6FWXCKFYRMZQME0WBGZ1AAMY2VBQ5XAFV8216EFNF2EPG6M5ZGHG9RG6EGED56TK9JESQ02Q7AAVBRAAARVBN9NHCN64KQ3SRRHYXB8RWRK4TSSC93XG8RWMQH4ZDJSBYDCEXFY6G3AWTZ0EZNCJJAYB98T4GNFWZMN81AVYCQHXT1APX81AXCAYNK7J9XETF5CN1J1WV0BVA2BYG4VAMAW123REPN67JF1TNWPTADBMHS17N2V1GFYT8JRWX4TGM2996NXTEPMA8C2CDDE0CRY2A6HT8C5H2D6C62YGRSCF820C0G008","accepted":true} diff --git a/nixos/tests/taler/conf/client-ebics-keys.json b/nixos/tests/taler/conf/client-ebics-keys.json new file mode 100644 index 0000000000000..d599e86893de0 --- /dev/null +++ b/nixos/tests/taler/conf/client-ebics-keys.json @@ -0,0 +1 @@ +{"signature_private_key":"62109F020403038614N8CJ46YW6G20810M0090G4MRR84152080G00M2040G1G344ZCHJWVZJVC7X42WJ6H99ZM4MND4QA766HQXG24QCE3FMAZWVSDWJYRPNM0PVWB1AH39685XN11P7EEHDHW89W0XNDYB4EMYTMA771DVMQWV57NZA9C5QGAK2N7FGKE73020ENJF74N49DEQCXW78FQQNZSDPAC07Y0GNJCR53Q59Z97T14E29NRKFMFYF2CQK45TPAK8M80H8K275TG5FMW16YVTSK3YJBSJN0G7MT187QCNS6P25SJD9Q30K578H3YY30F6R852ZG904BW07PAZT29HTV5F3ZE5K86F4MAQ9H2H7C3AYG8P6SZEK60E0QWWX79M0SP2BAC0QT8310X0TDZ16298SBSD5SDFGQT6FG44BEQNES1KVW1JZYD8GSB6TQQZ3VDFE7N9SBNST820C0G0082G7ZNG0X14DZMZZNP1NY2FQCKTRWN6JJVJR8F6AF76PWSAJ2A6R4P8MAMAJATDC0HKKQ6M9JDTFXRX9J8TFPZSEWD2N3CYZP39PGJG26GE8F6EN5P367K2JX45W1GC0FJF2Q1Z131QMKMYQNKT87C8Q5WXXKZ010H6N6YD47PFHBM3KPTEJJ0MSVH9RTCCABKA5MJ7N262BPRVYD6Y4G0Q3TDNYKX62MRXB4GAGS1CEEEGMYZ6TSC2WMWTVJYJCG4ZSDM2D9GEJ5KTH2RTWSJCYHN6E807VQM3GMMZCFCQP4309H592WQE93R2Y93VWD6PDQRJEXSZJDM4F7XHB9BNGAP4S1T67971K1AJQ2PSJXC99KFF8D1JN7GH40E8ZC3W599Y0WHKSD69Q1SB0WCEQEGZ90G50C103FCEV1ZD5CXVQ4T2Q438DGWKVE1K2JVR4EAKYT9AQ7F7EQZ35J04ACC2XVXZ8DN2ZDRY70WDK36GDRGFKZ2SFPACAQ7QAYXJW4PT3VGXAXP78HD8X9T608FY92SJWEE235AAYR26FEYX9VKQ6RH7XJE9Z5QYHZDJBSNY3VJES8GW8FR0WXVVDH5NFYRNPC5H8TESBFYDQXWQW82G60G1Q8MNSTSWRPWZ9GDZ7P73RQFBKXVNFKVY5T2VH3JEW68PGR1P4MV75ZM4SHN7RTGB0BZQEMRRC9SNDTJ2NF9YCZG8DHES8AYHZKWXQN5GSJBE0MTP316PW23W2RVNJCVCS6TC28J6GGJ660JEET1T1G2YJ0R846NQRK575WT24DR2Q2PK1Q837VE2EN05KQMCD8C4SCDTTVRF4183080NT78B8YY25XM9EHV5M3B82WJY8ADA4VNX7BCNFDRHP9EWHXA0SRTEJJCNMRVW30YD1XYDRZ2VD3SEDE55A3VDTQFHPAZQ8C227RV2WN1H8WD8FSM5WTQXH4PX6R09ERRXWFY15B67JA1W0RGA77BJS5X4D7N9XDJZ8EDE1NHXHNETX71FR7FA979E6ESAE8CGK8SNDYC8Z5920M1G0JESJAYPYZREV826MBMVSMANNFTXNNXEGXJRQD27K12F8W0SNJC8B2C8W4CY9XYNXBBQG6T8TH0M8EB0HR5E36VKYB7A9H7JTPBNB7T24EV9HYX1Q38ZJ6DKABKRQGDMSDHVSHQ2Y77EZVH6KF3XNEGVBACVJ1ZPKSH2E8J963AW6K4S22N3JPMCCTYJZPQT001GCB7E91ZNW82G60G1F5TBF02RRWS5FENV7SVW0P2WXKM4KGZNP0H3T0SP1S8H2P9R5ARQYT1XB44D4C6N9DGXW0ZY9RTPDN7FV2ZZ809EKEVD0PFJNNE7QENVE3AJ9YB4CEQ30APTVY6DJBSE90MXVJ1DT7PG7P404J6A6VCCJVSSCH330X97D8E8GCJV8T3A5TMANN5CAC8M5RR22PJAMP3ZC9XVC","encryption_private_key":"62109F020403038614N8CJ46YW6G20810M0090G4MRR84152080G00M2040G1M43X9T1PGMHQDXM3307ZR8A6E00XHF5FZEAMSTS7RZ3JZVAP50C0FKX592V5AM55E38Q6HXJA8K8SVRYCTDV6CATPTRGPW5NQ3T38CJ77KW0V8H20FBJSB69N51TMKG6MW7GJ1VBR5WY0JJ1VM5Z061SHT0G61S0WNMDZDSEYJP8QC0WW63CDV1HFHYV63TCDQB16PV1SZ53AHB3P2H2CKWQVN8P00RBGS9A6TEA1NZCR8RWXDTB1KBWJWWNW5EV94PCH6YJPVRA0DB12TAC2AR9FP4BDJTZS322Z1ZRDCXMBZXPMRHFVECEPAJGER1DBXM1HWE8NBC7D1MR3RSV9PSRPTXT07WVKH1YRDQ8S1CPCRACXZ953YT2PGESGD2FFQ9JDDR8Q0HJAHTDC8S83K0ZRR20C0G0082G80G0166J2QHM55V7EYGBWJC3288XZ3GC3WR8C36VK5J567WJHTPSMX7PV5JYWPY6YNEAGGXZFN7KHEC20W1NJM50KQWYG51ANKB91GA67GD7184Q0QKZ8BRM3BXFDAV0E1W4ENYGVAQ2TZN8ZBC3PTMNGNB5PG9DCPZADZVAFW8D3N1QMTWD5Q4JAWSHDBTR43AYMQ0NMVH4BSDFCAHKEHVCDPZTNPZ79BS52XPC36B95YDRSWFJ4ZS0VFS4KNQTJBHFXVS017N892FMBX3600TMPPB6QX0XTB03Q2CS755CXERJ75ZGE3G6C01EQZ7W1VKEFKBKF83RZG6TTKTY4PHV7N8CF7NTXA5WF4WDQR60TZG6GG7NQ2S4XF9S8MD3NPKZ80WE7QZGZH5Y59VWVPWG3GJZG82G60G1VWXEXJQZHXZFSQGFA3JWWEB8TFRMGJ9HMRF9GQFX0XB5QQVVTFWK563SG6SR0GZFGXRZY6AS31VM3SN2SFMQS51GWHF9QS8Z40G29CG96NDW5XTTTQEHF8SADQVXYSKEC8WPES3278B1X0NDQSY8B9NJ083X1V1FSVBCKZB62B9M6HCCR3YFFJJVTCBP9DCJ75RXJZF8YYNC4183080VV30KFVNSRC3NTKW174J3MC415EK8MKGNN1J62X235SM49KYBG3MMENBV03FGMWH55QE35PTVKQG48M6VAJHGNNYTGXGT0X7C9YECGC700414Z74D42W08Q53TWC9AGFR035D93CBKYXPQ5RY3CGWPA3RGZTDGDEE4J6PVHCZX6RBYRYQG53XFE7E4VKRM5Q54N62WAY13KW60M1G0XGR76Y416G5ZCF6BEYKRPP183RPH1QV24KMQENQVB8F6KWAZD8YTPZGJGJK1YNAW7HWJDB9BH0QNXFVS0KFF54V6YNKDQ5NN3HT3P8DM00J5B5QBM7SZ3BKD7VYTF498BRMC9639047XVGDJ6ZNWYGSN01HK3J7PAW7ETHM2GH6B67RR3GAS2PMYKW7JS1W0MQKM5DGQJZCG82G602QH54XPJEQ57Z3S5BGWBQVXZ1V26YBB2211KC810DSNSM9FSWZ27F427XN9D3XHKKSJ5R66VNCT7EYSRT0JW7F2HRTXQWKEBJPNKPPTRHPS3TKNKH5TSTA2GCPS39VWQV1ZMDSX42P38550V7VEV7G953MA3Q9VF4613GRVWBGG1PXTDSYFHM1NYKVN98B21P7SRVPQV4ZCCS0A0R0WGQ4AEKG8JFSYPMC03TA8M2J5PN51PVZP6WZ1R167B92AQGM3YTKAK1QECQK1S4T2HV04Y2YJDCYK5AYVJQYXFYQPFP1CYSE3Q458BBN8RXW5QMJEPCTGK2N3H7K5T2ZJMWNJY27J53B08703K93X0JSM643V7Z95CBJNKVZR4QXRKHFPG5W79AKZ3WP9CXY43XXSHG0KN424","authentication_private_key":"62109F820403038614N8CJ46YW6G20810M0090G4MWR84153080G00M2040G1CZR41XW4ANY74SDV2B2ZEFWPR0E9RXRWFAVAXJHZBMSYXHE5F32X4PKDTVBNHVNYAQYXDXMFV9WCYZA16PKDCEWZ87FKKDH8EJP7NQZB5GK83HJ6KX3P04BD878D99QZ44EE6SYK3YJZMBEP9H1755FKR25RBH5VEYRZ9X2T2DTBV34SREH7F7A81KCC4ZWBRWP1EH179T4RH17R278A9834G2FNPAKST9HK7P878W85DW9XG9BJQ40YCDTP6DGACTKZBJ992M6BDJN69HQZ9GDHF73M2CV7C96YYH8YKMD3V3YXNR17PP0BMKAB30T7KGTDMM92XH6Z0CKA6VQSRFFA59H77GESQYT05SY4N2AGRM9468G1XYJ7M2PM3EXZAYM1GJ934PRGG965Z8KRVZEFF820C0G0082G80G05FQQ0106J3KPWETPQ35EPRCTP0K8VTBJTDZ7P6PG6CXM968G5AP2GN6640WVE6D5AB2J4RYSNTVF4X6JJWWSYDCB7JTDFK1W27T7ZMEQB2MA2K57J0S5QPN4PQAF4C19J962JV51YSFNECVDT328F8H05MJF05H76N61QX5VQ2REQ879M02B7QRQ9NH284XCWWTA5T8HSJ6BG70SCF7S0YJX8F3DQDB763X3D4RY8Y7HVPVBCR9JKJC521R2BHR12WYZ329KN38MVJ69NHJMH2BFT76TRQXY8N6ECEGVC9GWC1KCVSGKQE4XSP6ZYFFVFBNTEVR3653PMF9S9XYSJYBPKQBTF2Q4SY2D56B0Z5MN0A0R7YY1PGZN69A9RPT8H18QVQPJR1YTM1BBNTQHS6KC382G60G1R1Q8A547XJHMF7Z9BR8PC9YPKAMCRKMFNFD71MS5P8CY3TC81MYWRBVNBC7DJA7DCFXHTW3BMC0RCZ39SYY2PS29FQJQR7ZDT8H260P4X8Q8D5JYYAJFDBSY82BQY4K71V5PGYH6PS0Q79CRWWQJQ68JHESE2C829CMNXDWTCW21G9SZC6JJ32P6HSGQ1T5K8VD9B6GFPZ6XC183080SNXJNZ2MFM6GRPHEN3SHTNE9M63KWHG4EQBEPYYG7E5DH5Q4NYP19ZGJ192XV7GJFRSH14XB3DXR4GEZACKMMEMSFTFJ16NQXMYYXTRF4KY9NPYPSD3E65RMF9CDGWAGMT46RTWZE2PRSSRFS15Z0CQ0HCN5K9EG16GGE77E27DNN43XFD2G3YKYXAMDPF7CGT3K8V3T328ZE0M1G1SM8XBMTY9SWK7MXZWVR8SM5SBWDXZDC72DSYWFDBNJ5Y0D3NMVKVPRDGJ23SQM7H0JSXAY8SPBM8D5NNAKD6SRRRHN8VYSHMX2S21FB0594DRA8G8VY4D1XRR90G4VNHF06BSHWVPRCPC1R68ZPW0BGBBE3ZHWN344AD0HM7GCA3N8B0276YZHQRJ2YAZ4RKG7Q8V4VJ8Q9682G60G1F9CJZV9E4J9W42PBKY5GD66FHBYDPKAH54MQ5BX7VFXES4ZAR46BM6MRBM90FQQ7DRVXGVGER6NWJK2195XJXMKQHW7B36HGT12HK1QQFF1J7RB6QWJEEFXR2M7M4YK3CS00RRMYA5XKVNNPV2VZNBXKGJ5SCHGSN6GKDNXE0HSM2VMBKS794DGB7D4PYSEYKSB9BM57G1B4418303W3D38F61CC4RC8DNDZNH9S7N0E4AM97QVRPMY7FERHN0JM4VHD5ET49RYJVW9Y9GBREB801N4A70Z9EGCBM2WJZYSF9KXX8EVDFX37KDJ2G4MTJNR628QV206M1QJ4P4EKS04D9GB8TTQNHJCXJ1PX9PJ44WYGAPKQQP5ENN1VCJAHVXTD5BXHJZYASWZ8QZJY7V760W36ER0","submitted_ini":true,"submitted_hia":true} diff --git a/nixos/tests/taler/conf/exchange-account.json b/nixos/tests/taler/conf/exchange-account.json new file mode 100644 index 0000000000000..4bc476ff894f9 --- /dev/null +++ b/nixos/tests/taler/conf/exchange-account.json @@ -0,0 +1,16 @@ +[ + { + "operation": "exchange-enable-wire-0", + "arguments": { + "payto_uri": "payto://x-taler-bank/bank:8082/exchange?receiver-name=Exchange", + "debit_restrictions": [], + "credit_restrictions": [], + "priority": 0, + "validity_start": { + "t_s": 1725886541 + }, + "master_sig_add": "68WDT3JX1S5GQ9D3RZWXQVZK9AHFZ46YY5DA993720YA3SCBR4SW3X09NH5DECTXGWBKSN0MGKE1ANA9QZ95SKSNYPS9T9G46PCJC20", + "master_sig_wire": "39CEN9007DEXXMSDFZX1R2YYNANZYAFHX4EZC4ZX3C8DQEYT83JNVCVYMWYDGWEX6S891ZPXD6QHJE9J41YV9EN703Q0NM0MVE4FP18" + } + } +] diff --git a/nixos/tests/taler/conf/private.key b/nixos/tests/taler/conf/private.key new file mode 100644 index 0000000000000..3025c97740ad7 --- /dev/null +++ b/nixos/tests/taler/conf/private.key @@ -0,0 +1 @@ +ƒØ'IÕ‚–v&©ˆû¾¢¶ßH{VWýrƒ¶CjÜ>¦ \ No newline at end of file diff --git a/nixos/tests/taler/conf/taler-accounts.conf b/nixos/tests/taler/conf/taler-accounts.conf new file mode 100644 index 0000000000000..9246c25a83ad2 --- /dev/null +++ b/nixos/tests/taler/conf/taler-accounts.conf @@ -0,0 +1,10 @@ +[exchange-account-test] +PAYTO_URI = payto://x-taler-bank/bank:8082/exchange?receiver-name=Exchange +ENABLE_DEBIT = YES +ENABLE_CREDIT = YES + +[exchange-accountcredentials-test] +WIRE_GATEWAY_URL = http://bank:8082/accounts/exchange/taler-wire-gateway/ +WIRE_GATEWAY_AUTH_METHOD = BASIC +USERNAME = exchange +PASSWORD = exchange diff --git a/nixos/tests/taler/conf/taler-denominations.conf b/nixos/tests/taler/conf/taler-denominations.conf new file mode 100644 index 0000000000000..e0c46ba9d6f6f --- /dev/null +++ b/nixos/tests/taler/conf/taler-denominations.conf @@ -0,0 +1,95 @@ +[COIN-KUDOS-n1-t1726827661] +VALUE = KUDOS:0.1 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 6 years +FEE_WITHDRAW = KUDOS:0 +FEE_DEPOSIT = KUDOS:0 +FEE_REFRESH = KUDOS:0 +FEE_REFUND = KUDOS:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[COIN-KUDOS-n2-t1726827661] +VALUE = KUDOS:0.2 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 6 years +FEE_WITHDRAW = KUDOS:0 +FEE_DEPOSIT = KUDOS:0 +FEE_REFRESH = KUDOS:0 +FEE_REFUND = KUDOS:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[COIN-KUDOS-n3-t1726827661] +VALUE = KUDOS:0.4 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 6 years +FEE_WITHDRAW = KUDOS:0 +FEE_DEPOSIT = KUDOS:0 +FEE_REFRESH = KUDOS:0 +FEE_REFUND = KUDOS:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[COIN-KUDOS-n4-t1726827661] +VALUE = KUDOS:0.8 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 6 years +FEE_WITHDRAW = KUDOS:0 +FEE_DEPOSIT = KUDOS:0 +FEE_REFRESH = KUDOS:0 +FEE_REFUND = KUDOS:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[COIN-KUDOS-n5-t1726827661] +VALUE = KUDOS:1.6 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 6 years +FEE_WITHDRAW = KUDOS:0 +FEE_DEPOSIT = KUDOS:0 +FEE_REFRESH = KUDOS:0 +FEE_REFUND = KUDOS:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[COIN-KUDOS-n6-t1726827661] +VALUE = KUDOS:3.2 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 6 years +FEE_WITHDRAW = KUDOS:0 +FEE_DEPOSIT = KUDOS:0 +FEE_REFRESH = KUDOS:0 +FEE_REFUND = KUDOS:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[COIN-KUDOS-n7-t1726827661] +VALUE = KUDOS:6.4 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 6 years +FEE_WITHDRAW = KUDOS:0 +FEE_DEPOSIT = KUDOS:0 +FEE_REFRESH = KUDOS:0 +FEE_REFUND = KUDOS:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA + +[COIN-KUDOS-n8-t1726827661] +VALUE = KUDOS:12.8 +DURATION_WITHDRAW = 7 days +DURATION_SPEND = 2 years +DURATION_LEGAL = 6 years +FEE_WITHDRAW = KUDOS:0 +FEE_DEPOSIT = KUDOS:0 +FEE_REFRESH = KUDOS:0 +FEE_REFUND = KUDOS:0 +RSA_KEYSIZE = 2048 +CIPHER = RSA diff --git a/nixos/tests/taler/default.nix b/nixos/tests/taler/default.nix new file mode 100644 index 0000000000000..00ce30ded6b6d --- /dev/null +++ b/nixos/tests/taler/default.nix @@ -0,0 +1,7 @@ +{ + system ? builtins.currentSystem, + pkgs ? import ../../.. { inherit system; }, +}: +{ + basic = import ./tests/basic.nix { inherit system pkgs; }; +} diff --git a/nixos/tests/taler/tests/basic.nix b/nixos/tests/taler/tests/basic.nix new file mode 100644 index 0000000000000..3eeb50b04fa98 --- /dev/null +++ b/nixos/tests/taler/tests/basic.nix @@ -0,0 +1,270 @@ +import ../../make-test-python.nix ( + { pkgs, lib, ... }: + let + cfgNodes = pkgs.callPackage ../common/nodes.nix { inherit lib; }; + in + { + # NOTE: The Nexus conversion subtest requires internet access, so to run it + # you must run the test with: + # - nix run .#nixosTests.taler.basic.driver + # or interactively: + # - nix run .#nixosTests.taler.basic.driverInteractive + # - run_tests() + + name = "GNU Taler Basic Test"; + meta = { + maintainers = lib.teams.ngi.members; + }; + + # Taler components virtual-machine nodes + nodes = { + inherit (cfgNodes.nodes) + bank + client + exchange + merchant + ; + }; + + # TODO: make tests for each component? + testScript = + { nodes, ... }: + let + cfgScripts = pkgs.callPackage ../common/scripts.nix { inherit lib pkgs nodes; }; + + inherit (cfgNodes) CURRENCY FIAT_CURRENCY; + inherit (cfgScripts) commonScripts; + + bankConfig = nodes.bank.config.environment.etc."libeufin/libeufin.conf".source; + bankSettings = nodes.bank.services.libeufin.settings.libeufin-bank; + nexusSettings = nodes.bank.services.libeufin.nexus.settings; + + # Bank admin account credentials + AUSER = "admin"; + APASS = "admin"; + + TUSER = "testUser"; + TPASS = "testUser"; + + exchangeAccount = ../conf/exchange-account.json; + in + + # python + '' + import json + + # import common scripts + ${commonScripts} + + + # NOTE: start components up individually so they don't conflict before their setup is done + bank.start() + client.start() + bank.wait_for_open_port(8082) + + with subtest("Set up Libeufin bank"): + # Modify admin account password, increase debit threshold + systemd_run(bank, 'libeufin-bank passwd -c "${bankConfig}" "${AUSER}" "${APASS}"', "libeufin-bank") + systemd_run(bank, 'libeufin-bank edit-account -c ${bankConfig} --debit_threshold="${bankSettings.CURRENCY}:1000000" ${AUSER}', "libeufin-bank") + + # NOTE: the exchange is enabled before the bank starts using the `initialAccounts` option + # TODO: just use that option instead of this? + with subtest("Register bank accounts"): + # username, password, name + register_bank_account("testUser", "testUser", "User") + register_bank_account("merchant", "merchant", "Merchant") + + + exchange.start() + exchange.wait_for_open_port(8081) + + + with subtest("Set up exchange"): + exchange.wait_until_succeeds("taler-exchange-offline download sign upload") + # Enable exchange wire account + exchange.succeed('taler-exchange-offline upload < ${exchangeAccount}') + + # NOTE: cannot deposit coins/pay merchant if wire fees are not set up + exchange.succeed('taler-exchange-offline wire-fee now x-taler-bank "${CURRENCY}:0" "${CURRENCY}:0" upload') + exchange.succeed('taler-exchange-offline global-fee now "${CURRENCY}:0" "${CURRENCY}:0" "${CURRENCY}:0" 1h 6a 0 upload') + + + # Verify that exchange keys exist + bank.succeed("curl -s http://exchange:8081/keys") + + + merchant.start() + merchant.wait_for_open_port(8083) + + + with subtest("Set up merchant"): + # Create default instance (similar to admin) + succeed(merchant, [ + "curl -X POST", + "-H 'Authorization: Bearer secret-token:super_secret'", + """ + --data '{ + "auth": { "method": "external" }, + "id": "default", + "name": "default", + "user_type": "business", + "address": {}, + "jurisdiction": {}, + "use_stefan": true, + "default_wire_transfer_delay": { "d_us": 3600000000 }, + "default_pay_delay": { "d_us": 3600000000 } + }' + """, + "-sSfL 'http://merchant:8083/management/instances'" + ]) + # Register bank account address + succeed(merchant, [ + "curl -X POST", + "-H 'Content-Type: application/json'", + """ + --data '{ + "payto_uri": "payto://x-taler-bank/bank:8082/merchant?receiver-name=Merchant", + "credit_facade_url": "http://bank:8082/accounts/merchant/taler-revenue/", + "credit_facade_credentials":{"type":"basic","username":"merchant","password":"merchant"} + }' + """, + "-sSfL 'http://merchant:8083/private/accounts'" + ]) + # Register a new product to be ordered + succeed(merchant, [ + "curl -X POST", + "-H 'Content-Type: application/json'", + """ + --data '{ + "product_id": "1", + "description": "Product with id 1 and price 1", + "price": "${CURRENCY}:1", + "total_stock": 20, + "unit": "packages", + "next_restock": { "t_s": "never" } + }' + """, + "-sSfL 'http://merchant:8083/private/products'" + ]) + + + client.succeed("curl -s http://exchange:8081/") + + + # Make a withdrawal from the CLI wallet + with subtest("Make a withdrawal from the CLI wallet"): + balanceWanted = "${CURRENCY}:10" + + # Register exchange + with subtest("Register exchange"): + wallet_cli("exchanges add http://exchange:8081/") + wallet_cli("exchanges accept-tos http://exchange:8081/") + + # Request withdrawal from the bank + withdrawal = json.loads( + succeed(client, [ + "curl -X POST", + "-u ${TUSER}:${TPASS}", + "-H 'Content-Type: application/json'", + f"""--data '{{"amount": "{balanceWanted}"}}'""", # double brackets escapes them + "-sSfL 'http://bank:8082/accounts/${TUSER}/withdrawals'" + ]) + ) + + # Accept & confirm withdrawal + with subtest("Accept & confirm withdrawal"): + wallet_cli(f"withdraw accept-uri {withdrawal["taler_withdraw_uri"]} --exchange http://exchange:8081/") + succeed(client, [ + "curl -X POST", + "-u ${TUSER}:${TPASS}", + "-H 'Content-Type: application/json'", + f"-sSfL 'http://bank:8082/accounts/${TUSER}/withdrawals/{withdrawal["withdrawal_id"]}/confirm'" + ]) + + # Process transactions + wallet_cli("run-until-done") + + verify_balance(balanceWanted) + + + with subtest("Pay for an order"): + balanceWanted = "${CURRENCY}:9" # after paying + + # Create an order to be paid + response = json.loads( + succeed(merchant, [ + "curl -X POST", + "-H 'Content-Type: application/json'", + """ + --data '{ + "order": { "amount": "${CURRENCY}:1", "summary": "Test Order" }, + "inventory_products": [{ "product_id": "1", "quantity": 1 }] + }' + """, + "-sSfL 'http://merchant:8083/private/orders'" + ]) + ) + order_id = response["order_id"] + token = response["token"] + + # Get order pay URI + response = json.loads( + succeed(merchant, [ + "curl -sSfL", + f"http://merchant:8083/private/orders/{order_id}" + ]) + ) + wallet_cli("run-until-done") + + # Process transaction + wallet_cli(f"""handle-uri -y '{response["taler_pay_uri"]}'""") + wallet_cli("run-until-done") + + verify_balance(balanceWanted) + + # Only run Nexus conversion test if ebics server is available + (status, out) = bank.execute('curl -sSfL "${nexusSettings.nexus-ebics.HOST_BASE_URL}"') + + if status != 0: + bank.log("Can't connect to ebics server. Skipping Nexus conversion subtest.") + else: + with subtest("Libeufin Nexus currency conversion"): + regionalWanted = "20" + + # Setup Nexus ebics keys + systemd_run(bank, "libeufin-nexus ebics-setup -L debug -c /etc/libeufin/libeufin.conf", "libeufin-nexus") + + # Set currency conversion rates (1:1) + succeed(bank, [ + "curl -X POST", + "-H 'Content-Type: application/json'", + "-u ${AUSER}:${APASS}", + """ + --data '{ + "cashin_ratio": "1", + "cashin_fee": "${CURRENCY}:0", + "cashin_tiny_amount": "${CURRENCY}:0.01", + "cashin_rounding_mode": "nearest", + "cashin_min_amount": "${FIAT_CURRENCY}:1", + "cashout_ratio": "1", + "cashout_fee": "${FIAT_CURRENCY}:0", + "cashout_tiny_amount": "${FIAT_CURRENCY}:0.01", + "cashout_rounding_mode": "nearest", + "cashout_min_amount": "${CURRENCY}:1" + }' + """, + "-sSfL 'http://bank:8082/conversion-info/conversion-rate'" + ]) + + # Make fake transaction (we only need reservePub) + response = wallet_cli("""api 'acceptManualWithdrawal' '{ "exchangeBaseUrl":"http://exchange:8081/", "amount":"${CURRENCY}:5" }'""") + reservePub = json.loads(response)["result"]["reservePub"] + + # Convert fiat currency to regional + systemd_run(bank, f"""libeufin-nexus testing fake-incoming -c ${bankConfig} --amount="${FIAT_CURRENCY}:{regionalWanted}" --subject="{reservePub}" "payto://iban/CH4740123RW4167362694" """, "libeufin-nexus") + wallet_cli("run-until-done") + + verify_conversion(regionalWanted) + ''; + } +)