1{
2 lib,
3 pkgs,
4 nodes,
5 ...
6}:
7
8let
9 cfgNodes = pkgs.callPackage ./nodes.nix { inherit lib; };
10 bankConfig = nodes.bank.environment.etc."libeufin/libeufin.conf".source;
11
12 inherit (cfgNodes) CURRENCY FIAT_CURRENCY;
13in
14{
15 commonScripts =
16 # python
17 ''
18 def succeed(machine, commands):
19 """A more convenient `machine.succeed` that supports multi-line inputs"""
20 flattened_commands = [c.replace("\n", "") for c in commands] # flatten multi-line
21 return machine.succeed(" ".join(flattened_commands))
22
23
24 def systemd_run(machine, cmd, user="nobody", group="nobody"):
25 """Execute command as a systemd DynamicUser"""
26 machine.log(f"Executing command (via systemd-run): \"{cmd}\"")
27
28 (status, out) = machine.execute( " ".join([
29 "systemd-run",
30 "--service-type=exec",
31 "--quiet",
32 "--wait",
33 "-E PATH=\"$PATH\"",
34 "-p StandardOutput=journal",
35 "-p StandardError=journal",
36 "-p DynamicUser=yes",
37 f"-p Group={group}" if group != "nobody" else "",
38 f"-p User={user}" if user != "nobody" else "",
39 f"$SHELL -c '{cmd}'"
40 ]) )
41
42 if status != 0:
43 raise Exception(f"systemd_run failed (status {status})")
44
45 machine.log("systemd-run finished successfully")
46
47
48 def register_bank_account(username, password, name, is_exchange=False):
49 """Register Libeufin bank account for the x-taler-bank wire method"""
50 return systemd_run(bank, " ".join([
51 'libeufin-bank',
52 'create-account',
53 '-c ${bankConfig}',
54 f'--username {username}',
55 f'--password {password}',
56 f'--name {name}',
57 f'--payto_uri="payto://x-taler-bank/bank:8082/{username}?receiver-name={name}"',
58 '--exchange' if (is_exchange or username.lower()=="exchange") else ' '
59 ]),
60 user="libeufin-bank")
61
62
63 def wallet_cli(command):
64 """Wrapper for the Taler CLI wallet"""
65 return client.succeed(
66 "taler-wallet-cli "
67 "--no-throttle " # don't do any request throttling
68 + command
69 )
70
71 # https://docs.taler.net/core/api-corebank.html#authentication
72 def create_token(machine, username, password):
73 """Create a read-write bank access token for a user"""
74 response = succeed(machine, [
75 "curl -X POST",
76 f"-u {username}:{password}",
77 "-H 'Content-Type: application/json'",
78 """
79 --data '{ "scope": "readwrite" }'
80 """,
81 f"-sSfL 'http://bank:8082/accounts/{username}/token'"
82 ])
83 return json.loads(response)["access_token"]
84
85
86 # Basic auth is deprecated, so exchange credentials must be set at
87 # runtime because it requires a token from the bank.
88 def create_exchange_auth(token: str):
89 template = f"""
90 [exchange-accountcredentials-test]
91 WIRE_GATEWAY_URL = http://bank:8082/accounts/exchange/taler-wire-gateway/
92 WIRE_GATEWAY_AUTH_METHOD = BEARER
93 TOKEN = "{token}"
94 """
95 return "\n".join([line.strip() for line in template.splitlines()])
96
97 def verify_balance(balanceWanted: str):
98 """Compare Taler CLI wallet balance with expected amount"""
99 balance = wallet_cli("balance --json")
100 try:
101 balanceGot = json.loads(balance)["balances"][0]["available"]
102 except:
103 balanceGot = "${CURRENCY}:0"
104
105 # Compare balance with expected value
106 if balanceGot != balanceWanted:
107 client.fail(f'echo Wanted balance: "{balanceWanted}", got: "{balanceGot}"')
108 else:
109 client.succeed(f"echo Withdraw successfully made. New balance: {balanceWanted}")
110
111
112 def verify_conversion(regionalWanted: str, accessToken: str):
113 """Compare converted Libeufin Nexus funds with expected regional currency"""
114 # Get transaction details
115 response = json.loads(
116 succeed(bank, [
117 "curl -sSfL",
118 f"-H 'Authorization: Bearer {accessToken}'",
119 # TODO: get exchange from config?
120 "http://bank:8082/accounts/exchange/transactions"
121 ])
122 )
123 amount = response["transactions"][0]["amount"].split(":") # CURRENCY:VALUE
124 currencyGot, regionalGot = amount
125
126 # Check conversion (1:1 ratio)
127 if (regionalGot != regionalWanted) or (currencyGot != "${CURRENCY}"):
128 client.fail(f'echo Wanted "${CURRENCY}:{regionalWanted}", got: "{currencyGot}:{regionalGot}"')
129 else:
130 client.succeed(f'echo Conversion successfully made: "${FIAT_CURRENCY}:{regionalWanted}" -> "{currencyGot}:{regionalGot}"')
131 '';
132}