-
Notifications
You must be signed in to change notification settings - Fork 27
/
test_transactions.py
401 lines (319 loc) · 15.4 KB
/
test_transactions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
import logging
import hypothesis
import hypothesis.strategies as st
import pytest
from cardano_node_tests.utils import clusterlib
from cardano_node_tests.utils import helpers
LOGGER = logging.getLogger(__name__)
@pytest.fixture(scope="module")
def temp_dir(tmp_path_factory):
"""Create a temporary dir and change to it."""
tmp_path = tmp_path_factory.mktemp("test_transactions")
with helpers.change_cwd(tmp_path):
yield tmp_path
# use the "temp_dir" fixture for all tests automatically
pytestmark = pytest.mark.usefixtures("temp_dir")
class TestBasic:
@pytest.fixture(scope="class")
def payment_addrs(self, cluster_session, addrs_data_session, request):
"""Create 2 new payment addresses."""
addrs = helpers.create_payment_addr_records(
"addr_basic0", "addr_basic1", cluster_obj=cluster_session
)
# fund source addresses
helpers.fund_from_faucet(
addrs[0],
cluster_obj=cluster_session,
faucet_data=addrs_data_session["user1"],
request=request,
)
return addrs
def test_transfer_funds(self, cluster_session, addrs_data_session, payment_addrs):
"""Send funds from faucet to payment address."""
cluster = cluster_session
amount = 2000
src_address = addrs_data_session["user1"]["payment_addr"]
dst_address = payment_addrs[0].address
src_init_balance = cluster.get_address_balance(src_address)
dst_init_balance = cluster.get_address_balance(dst_address)
destinations = [clusterlib.TxOut(address=dst_address, amount=amount)]
tx_files = clusterlib.TxFiles(
signing_key_files=[addrs_data_session["user1"]["payment_key_pair"].skey_file]
)
tx_raw_data = cluster.send_funds(
src_address=src_address, destinations=destinations, tx_files=tx_files,
)
cluster.wait_for_new_block(new_blocks=2)
assert (
cluster.get_address_balance(src_address)
== src_init_balance - tx_raw_data.fee - len(destinations) * amount
), f"Incorrect balance for source address `{src_address}`"
assert (
cluster.get_address_balance(dst_address) == dst_init_balance + amount
), f"Incorrect balance for destination address `{dst_address}`"
def test_transfer_all_funds(self, cluster_session, payment_addrs):
"""Send ALL funds from one payment address to another."""
cluster = cluster_session
src_address = payment_addrs[0].address
dst_address = payment_addrs[1].address
src_init_balance = cluster.get_address_balance(src_address)
dst_init_balance = cluster.get_address_balance(dst_address)
# amount value -1 means all available funds
destinations = [clusterlib.TxOut(address=payment_addrs[1].address, amount=-1)]
tx_files = clusterlib.TxFiles(signing_key_files=[payment_addrs[0].skey_file])
tx_raw_data = cluster.send_funds(
src_address=src_address, destinations=destinations, tx_files=tx_files,
)
cluster.wait_for_new_block(new_blocks=2)
assert (
cluster.get_address_balance(src_address) == 0
), f"Incorrect balance for source address `{src_address}`"
assert (
cluster.get_address_balance(dst_address)
== dst_init_balance + src_init_balance - tx_raw_data.fee
), f"Incorrect balance for destination address `{dst_address}`"
def test_get_txid(self, cluster_session, addrs_data_session, payment_addrs):
"""Get transaction ID (txid) from transaction body.
Transaction ID is a hash of transaction body and doesn't change for a signed TX.
"""
cluster = cluster_session
src_address = addrs_data_session["user1"]["payment_addr"]
dst_address = payment_addrs[0].address
destinations = [clusterlib.TxOut(address=dst_address, amount=2000)]
tx_files = clusterlib.TxFiles(
signing_key_files=[addrs_data_session["user1"]["payment_key_pair"].skey_file]
)
tx_raw_data = cluster.send_funds(
src_address=src_address, destinations=destinations, tx_files=tx_files,
)
cluster.wait_for_new_block(new_blocks=2)
txid = cluster.get_txid(tx_raw_data.out_file)
utxo = cluster.get_utxo(src_address)
assert len(txid) == 64
assert txid in (u.utxo_hash for u in utxo)
class Test10InOut:
@pytest.fixture(scope="class")
def payment_addrs(self, cluster_session, addrs_data_session, request):
"""Create 11 new payment addresses."""
addrs = helpers.create_payment_addr_records(
*[f"addr_10_in_out{i}" for i in range(11)], cluster_obj=cluster_session,
)
# fund source addresses
helpers.fund_from_faucet(
addrs[0],
cluster_obj=cluster_session,
faucet_data=addrs_data_session["user1"],
request=request,
)
return addrs
def test_10_transactions(self, cluster_session, addrs_data_session, payment_addrs):
"""Send 10 transactions from faucet to payment address.
Test 10 different UTXOs in addr0.
"""
cluster = cluster_session
no_of_transactions = len(payment_addrs) - 1
src_address = addrs_data_session["user1"]["payment_addr"]
dst_address = payment_addrs[0].address
src_init_balance = cluster.get_address_balance(src_address)
dst_init_balance = cluster.get_address_balance(dst_address)
tx_files = clusterlib.TxFiles(
signing_key_files=[addrs_data_session["user1"]["payment_key_pair"].skey_file]
)
ttl = cluster.calculate_tx_ttl()
fee = cluster.calculate_tx_fee(
src_address, dst_addresses=[dst_address], tx_files=tx_files, ttl=ttl,
)
amount = int(fee / no_of_transactions + 1000)
destinations = [clusterlib.TxOut(address=dst_address, amount=amount)]
for __ in range(no_of_transactions):
cluster.send_funds(
src_address=src_address,
destinations=destinations,
tx_files=tx_files,
fee=fee,
ttl=ttl,
)
cluster.wait_for_new_block(new_blocks=2)
assert (
cluster.get_address_balance(src_address)
== src_init_balance - fee * no_of_transactions - amount * no_of_transactions
), f"Incorrect balance for source address `{src_address}`"
assert (
cluster.get_address_balance(dst_address)
== dst_init_balance + amount * no_of_transactions
), f"Incorrect balance for destination address `{dst_address}`"
def test_transaction_to_10_addrs(self, cluster_session, payment_addrs):
"""Send 1 transaction from one payment address to 10 payment addresses."""
cluster = cluster_session
src_address = payment_addrs[0].address
# addr1..addr10
dst_addresses = [payment_addrs[i].address for i in range(1, len(payment_addrs))]
src_init_balance = cluster.get_address_balance(src_address)
dst_init_balances = {addr: cluster.get_address_balance(addr) for addr in dst_addresses}
tx_files = clusterlib.TxFiles(signing_key_files=[payment_addrs[0].skey_file])
ttl = cluster.calculate_tx_ttl()
fee = cluster.calculate_tx_fee(
src_address, dst_addresses=dst_addresses, tx_files=tx_files, ttl=ttl,
)
amount = int((cluster.get_address_balance(src_address) - fee) / len(dst_addresses))
destinations = [clusterlib.TxOut(address=addr, amount=amount) for addr in dst_addresses]
cluster.send_funds(
src_address=src_address, destinations=destinations, tx_files=tx_files, fee=fee, ttl=ttl,
)
cluster.wait_for_new_block(new_blocks=2)
assert cluster.get_address_balance(src_address) == src_init_balance - fee - amount * len(
dst_addresses
), f"Incorrect balance for source address `{src_address}`"
for addr in dst_addresses:
assert (
cluster.get_address_balance(addr) == dst_init_balances[addr] + amount
), f"Incorrect balance for destination address `{addr}`"
class TestNotBalanced:
@pytest.fixture(scope="class")
def payment_addr_rec(self, cluster_session):
"""Create 1 new payment address."""
return helpers.create_payment_addr_records(
"addr_not_balanced0", cluster_obj=cluster_session
)[0]
def test_negative_change(self, cluster_session, addrs_data_session, payment_addr_rec, temp_dir):
"""Build a transaction with a negative change."""
cluster = cluster_session
src_address = addrs_data_session["user1"]["payment_addr"]
dst_address = payment_addr_rec.address
tx_files = clusterlib.TxFiles(
signing_key_files=[addrs_data_session["user1"]["payment_key_pair"].skey_file]
)
ttl = cluster.calculate_tx_ttl()
fee = cluster.calculate_tx_fee(
src_address, dst_addresses=[dst_address], tx_files=tx_files, ttl=ttl,
)
src_addr_highest_utxo = cluster.get_utxo_with_highest_amount(src_address)
# use only the UTXO with highest amount
txins = [src_addr_highest_utxo]
# try to transfer +1 Lovelace more than available and use a negative change (-1)
txouts = [
clusterlib.TxOut(address=dst_address, amount=src_addr_highest_utxo.amount - fee + 1),
clusterlib.TxOut(address=src_address, amount=-1),
]
assert txins[0].amount - txouts[0].amount - fee == txouts[-1].amount
with pytest.raises(clusterlib.CLIError) as excinfo:
cluster.build_raw_tx_bare(
out_file=temp_dir / "tx.body",
txins=txins,
txouts=txouts,
tx_files=tx_files,
fee=fee,
ttl=ttl,
)
assert "option --tx-out: Failed reading" in str(excinfo.value)
@hypothesis.given(transfer_add=st.integers(), change_amount=st.integers(min_value=0))
@hypothesis.settings(deadline=None)
def test_wrong_balance(
self,
cluster_session,
addrs_data_session,
payment_addr_rec,
temp_dir,
transfer_add,
change_amount,
):
"""Build a transaction with unbalanced change."""
# we want to test only unbalanced transactions
hypothesis.assume((transfer_add + change_amount) != 0)
cluster = cluster_session
src_address = addrs_data_session["user1"]["payment_addr"]
dst_address = payment_addr_rec.address
src_addr_highest_utxo = cluster.get_utxo_with_highest_amount(src_address)
fee = 200_000
# add to `transferred_amount` the value from test's parameter to unbalance the transaction
transferred_amount = src_addr_highest_utxo.amount - fee + transfer_add
# make sure the change amount is valid
hypothesis.assume(0 <= transferred_amount <= src_addr_highest_utxo.amount)
out_file_tx = temp_dir / f"{clusterlib.get_timestamped_rand_str()}_tx.body"
tx_files = clusterlib.TxFiles(
signing_key_files=[addrs_data_session["user1"]["payment_key_pair"].skey_file]
)
ttl = cluster.calculate_tx_ttl()
# use only the UTXO with highest amount
txins = [src_addr_highest_utxo]
txouts = [
clusterlib.TxOut(address=dst_address, amount=transferred_amount),
# Add the value from test's parameter to unbalance the transaction. Since the correct
# change amount here is 0, the value from test's parameter can be used directly.
clusterlib.TxOut(address=src_address, amount=change_amount),
]
# it should be possible to build and sign an unbalanced transaction
cluster.build_raw_tx_bare(
out_file=out_file_tx, txins=txins, txouts=txouts, tx_files=tx_files, fee=fee, ttl=ttl,
)
out_file_signed = cluster.sign_tx(
tx_body_file=out_file_tx, signing_key_files=tx_files.signing_key_files,
)
# it should NOT be possible to submit an unbalanced transaction
with pytest.raises(clusterlib.CLIError) as excinfo:
cluster.submit_tx(out_file_signed)
assert "ValueNotConservedUTxO" in str(excinfo.value)
def test_negative_fee(cluster_session, addrs_data_session):
"""Send a transaction with negative fee (-1)."""
cluster = cluster_session
payment_addr_rec = helpers.create_payment_addr_records(
"addr_negative_fee0", cluster_obj=cluster_session
)[0]
src_address = addrs_data_session["user1"]["payment_addr"]
tx_files = clusterlib.TxFiles(
signing_key_files=[addrs_data_session["user1"]["payment_key_pair"].skey_file]
)
destinations = [clusterlib.TxOut(address=payment_addr_rec.address, amount=10)]
with pytest.raises(clusterlib.CLIError) as excinfo:
cluster.send_funds(
src_address=src_address, destinations=destinations, tx_files=tx_files, fee=-1,
)
assert "option --fee: cannot parse value" in str(excinfo.value)
def test_past_ttl(cluster_session, addrs_data_session):
"""Send a transaction with ttl in the past."""
cluster = cluster_session
payment_addr_rec = helpers.create_payment_addr_records("addr_past_ttl0", cluster_obj=cluster)[0]
src_address = addrs_data_session["user1"]["payment_addr"]
tx_files = clusterlib.TxFiles(
signing_key_files=[addrs_data_session["user1"]["payment_key_pair"].skey_file]
)
destinations = [clusterlib.TxOut(address=payment_addr_rec.address, amount=1)]
ttl = cluster.get_last_block_slot_no() - 1
fee = cluster.calculate_tx_fee(src_address, txouts=destinations, tx_files=tx_files, ttl=ttl)
# it should be possible to build and sign a transaction with ttl in the past
tx_raw_data = cluster.build_raw_tx(
src_address=src_address, txouts=destinations, tx_files=tx_files, fee=fee, ttl=ttl,
)
out_file_signed = cluster.sign_tx(
tx_body_file=tx_raw_data.out_file, signing_key_files=tx_files.signing_key_files,
)
# it should NOT be possible to submit a transaction with ttl in the past
with pytest.raises(clusterlib.CLIError) as excinfo:
cluster.submit_tx(out_file_signed)
assert "ExpiredUTxO" in str(excinfo.value)
def test_send_funds_to_reward_address(cluster_session, addrs_data_session, request):
"""Send funds from payment address to stake address."""
cluster = cluster_session
stake_addr_rec = helpers.create_stake_addr_records(
"addr_send_funds_to_reward_address0", cluster_obj=cluster
)[0]
payment_addr_rec = helpers.create_payment_addr_records(
"addr_send_funds_to_reward_address0",
cluster_obj=cluster,
stake_vkey_file=stake_addr_rec.vkey_file,
)[0]
# fund source address
helpers.fund_from_faucet(
payment_addr_rec,
cluster_obj=cluster,
faucet_data=addrs_data_session["user1"],
request=request,
)
tx_files = clusterlib.TxFiles(signing_key_files=[stake_addr_rec.skey_file])
destinations = [clusterlib.TxOut(address=stake_addr_rec.address, amount=1000)]
# it should NOT be possible to build a transaction using a stake address
with pytest.raises(clusterlib.CLIError) as excinfo:
cluster.build_raw_tx(
src_address=payment_addr_rec.address, txouts=destinations, tx_files=tx_files, fee=0,
)
assert "invalid address" in str(excinfo.value)