Skip to content

Commit 7bd90f9

Browse files
committed
Complete section: The Cryptocurrency: Wallets, Keys, and Transactions
1 parent 116c12d commit 7bd90f9

2 files changed

Lines changed: 170 additions & 0 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import pytest
2+
3+
from backend.wallet.transaction import Transaction
4+
from backend.wallet.wallet import Wallet
5+
6+
def test_transaction():
7+
sender_wallet = Wallet()
8+
recipient = 'recipient'
9+
amount = 50
10+
transaction = Transaction(sender_wallet, recipient, amount)
11+
12+
assert transaction.output[recipient] == amount
13+
assert transaction.output[sender_wallet.address] == sender_wallet.balance - amount
14+
15+
assert 'timestamp' in transaction.input
16+
assert transaction.input['amount'] == sender_wallet.balance
17+
assert transaction.input['address'] == sender_wallet.address
18+
assert transaction.input['public_key'] == sender_wallet.public_key
19+
assert Wallet.verify(
20+
transaction.input['public_key'],
21+
transaction.output,
22+
transaction.input['signature']
23+
)
24+
25+
def test_transaction_exceeds_balance():
26+
with pytest.raises(Exception, match='Amount exceeds balance'):
27+
Transaction(Wallet(), 'recipient', 9001)
28+
29+
def test_transaction_update_exceeds_balance():
30+
sender_wallet = Wallet()
31+
transaction = Transaction(sender_wallet, 'recipient', 50)
32+
33+
with pytest.raises(Exception, match='Amount exceeds balance'):
34+
transaction.update(sender_wallet, 'new_recipient', 9001)
35+
36+
def test_transaction_update():
37+
sender_wallet = Wallet()
38+
first_recipient = 'first_recipient'
39+
first_amount = 50
40+
transaction = Transaction(sender_wallet, first_recipient, first_amount)
41+
42+
next_recipient = 'next_recipient'
43+
next_amount = 75
44+
transaction.update(sender_wallet, next_recipient, next_amount)
45+
46+
assert transaction.output[next_recipient] == next_amount
47+
assert transaction.output[sender_wallet.address] ==\
48+
sender_wallet.balance - first_amount - next_amount
49+
assert Wallet.verify(
50+
transaction.input['public_key'],
51+
transaction.output,
52+
transaction.input['signature']
53+
)
54+
55+
to_first_again_amount = 25
56+
transaction.update(sender_wallet, first_recipient, to_first_again_amount)
57+
58+
assert transaction.output[first_recipient] == \
59+
first_amount + to_first_again_amount
60+
assert transaction.output[sender_wallet.address] ==\
61+
sender_wallet.balance - first_amount - next_amount - to_first_again_amount
62+
assert Wallet.verify(
63+
transaction.input['public_key'],
64+
transaction.output,
65+
transaction.input['signature']
66+
)
67+
68+
def test_valid_transaction():
69+
Transaction.is_valid_transaction(Transaction(Wallet(), 'recipient', 50))
70+
71+
def test_valid_transaction_with_invalid_outputs():
72+
sender_wallet = Wallet()
73+
transaction = Transaction(sender_wallet, 'recipient', 50)
74+
transaction.output[sender_wallet.address] = 9001
75+
76+
with pytest.raises(Exception, match='Invalid transaction output values'):
77+
Transaction.is_valid_transaction(transaction)
78+
79+
def test_valid_transaction_with_invalid_signature():
80+
transaction = Transaction(Wallet(), 'recipient', 50)
81+
transaction.input['signature'] = Wallet().sign(transaction.output)
82+
83+
with pytest.raises(Exception, match='Invalid signature'):
84+
Transaction.is_valid_transaction(transaction)

backend/wallet/transaction.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import time
2+
import uuid
3+
4+
from backend.wallet.wallet import Wallet
5+
6+
class Transaction:
7+
"""
8+
Document of an exchange in currency from a sender to one
9+
or more recipients.
10+
"""
11+
def __init__(self, sender_wallet, recipient, amount):
12+
self.id = str(uuid.uuid4())[0:8]
13+
self.output = self.create_output(
14+
sender_wallet,
15+
recipient,
16+
amount
17+
)
18+
self.input = self.create_input(sender_wallet, self.output)
19+
20+
def create_output(self, sender_wallet, recipient, amount):
21+
"""
22+
Structure the output data for the transaction.
23+
"""
24+
if amount > sender_wallet.balance:
25+
raise Exception('Amount exceeds balance')
26+
27+
output = {}
28+
output[recipient] = amount
29+
output[sender_wallet.address] = sender_wallet.balance - amount
30+
31+
return output
32+
33+
def create_input(self, sender_wallet, output):
34+
"""
35+
Structure the input data for the transaction.
36+
Sign the transaction and include the sender's public key and address
37+
"""
38+
return {
39+
'timestamp': time.time_ns(),
40+
'amount': sender_wallet.balance,
41+
'address': sender_wallet.address,
42+
'public_key': sender_wallet.public_key,
43+
'signature': sender_wallet.sign(output)
44+
}
45+
46+
def update(self, sender_wallet, recipient, amount):
47+
"""
48+
Update the transaction with an existing or new recipient.
49+
"""
50+
if amount > self.output[sender_wallet.address]:
51+
raise Exception('Amount exceeds balance')
52+
53+
if recipient in self.output:
54+
self.output[recipient] = self.output[recipient] + amount
55+
else:
56+
self.output[recipient] = amount
57+
58+
self.output[sender_wallet.address] = \
59+
self.output[sender_wallet.address] - amount
60+
61+
self.input = self.create_input(sender_wallet, self.output)
62+
63+
@staticmethod
64+
def is_valid_transaction(transaction):
65+
"""
66+
Validate a transaction.
67+
Raise an exception for invalid transactions.
68+
"""
69+
output_total = sum(transaction.output.values())
70+
71+
if transaction.input['amount'] != output_total:
72+
raise Exception('Invalid transaction output values')
73+
74+
if not Wallet.verify(
75+
transaction.input['public_key'],
76+
transaction.output,
77+
transaction.input['signature']
78+
):
79+
raise Exception('Invalid signature')
80+
81+
def main():
82+
transaction = Transaction(Wallet(), 'recipient', 15)
83+
print(f'transaction.__dict__: {transaction.__dict__}')
84+
85+
if __name__ == '__main__':
86+
main()

0 commit comments

Comments
 (0)