Skip to content
This repository was archived by the owner on Apr 3, 2026. It is now read-only.

Commit d65621d

Browse files
authored
feat: Exercise #5. Python Standard Library
BREAKING CHANGE: Exercise added
2 parents 4c03a81 + 6446f53 commit d65621d

File tree

5 files changed

+258
-0
lines changed

5 files changed

+258
-0
lines changed

5-standard-library/blockchain.py

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
# A built in module that provides functions from python standard library
2+
import functools
3+
import collections
4+
5+
from utils import hash_block, hash_string_256
6+
7+
MINING_REWARD = 10
8+
genesis_block = {
9+
'previous_hash': '',
10+
'index': 0,
11+
'transactions': [],
12+
'proof': 100
13+
}
14+
my_blockchain = [genesis_block]
15+
open_transactions = []
16+
waiting_for_input = True
17+
owner = 'Nicolas'
18+
participants = set([owner])
19+
20+
def add__line():
21+
print('------------------------------')
22+
23+
def generate_options_menu():
24+
add__line()
25+
print('Please choose an option:')
26+
print('1: Add a new transaction')
27+
print('2: Mine a new block')
28+
print('3: Output all blockchain blocks')
29+
print('4: Output all participants')
30+
print('h: Manipulate blockchain')
31+
print('q: Quit')
32+
add__line()
33+
34+
def get_user_input():
35+
user_input = input('Please enter your selection: ')
36+
add__line()
37+
return user_input
38+
39+
def valid_proof(transactions, last_hash, proof):
40+
guess = (str(transactions) + str(last_hash) + str(proof)).encode()
41+
guess_hash = hash_string_256(guess)
42+
# If the hash starts with 2 leading zeros, we consider it a valid proof
43+
return guess_hash[0:2] == '00'
44+
45+
def proof_of_work():
46+
last_block = my_blockchain[-1]
47+
hash_last_block = hash_block(last_block)
48+
proof = 0
49+
while not valid_proof(open_transactions, hash_last_block, proof):
50+
proof += 1
51+
return proof
52+
53+
54+
55+
def mine_block():
56+
last_block = my_blockchain[-1]
57+
hashed_block = hash_block(last_block)
58+
proof_of_work_value = proof_of_work()
59+
60+
# Old way to create a mining reward transaction, it will be replaced by an OrderedDict to keep the order of the elements
61+
# reward_transaction = {
62+
# 'sender': 'MINING',
63+
# 'recipient': owner,
64+
# 'amount': MINING_REWARD
65+
# }
66+
reward_transaction = collections.OrderedDict([('sender', 'MINING'), ('recipient', owner), ('amount', MINING_REWARD)])
67+
68+
block = {
69+
'previous_hash': hashed_block,
70+
'index': len(my_blockchain),
71+
'transactions': [open_transactions, reward_transaction],
72+
'proof': proof_of_work_value
73+
}
74+
75+
my_blockchain.append(block)
76+
print('Block added!')
77+
add__line()
78+
return True
79+
80+
def get_transaction_value():
81+
""" Returns the input of the user (a new transaction amount and its recipient) as a tuple """
82+
tx_recipient_input = input('Please enter the recipient of the transaction: ')
83+
tx_amount_input = float(input('Please enter your transaction input: '))
84+
add__line()
85+
86+
return tx_recipient_input, tx_amount_input
87+
88+
def take_last_blockchain_value():
89+
if len(my_blockchain) < 1:
90+
return None
91+
92+
return my_blockchain[-1]
93+
94+
def verify_transaction(transaction):
95+
sender_balance = get_balance(transaction['sender'])
96+
97+
if sender_balance >= transaction['amount']:
98+
return True
99+
return False
100+
101+
def add_transaction(sender, recipient, amount=1):
102+
"""
103+
Add a new transaction to the list of open transactions (which will be added to the next mined block)
104+
105+
Arguments:
106+
:sender: The sender of the coins.
107+
:recipient: The recipient of the coins.
108+
:amount: The amount of the transaction.
109+
"""
110+
111+
# An old way to create a transaction, it will be replaced by an OrderedDict to keep the order of the elements
112+
# new_transaction = {
113+
# 'sender': sender,
114+
# 'recipient': recipient,
115+
# 'amount': amount
116+
# }
117+
new_transaction = collections.OrderedDict([('sender', sender), ('recipient', recipient), ('amount', amount)])
118+
119+
if verify_transaction(new_transaction):
120+
open_transactions.append(new_transaction)
121+
participants.add(sender)
122+
participants.add(recipient)
123+
else:
124+
print('Transaction failed! Not enough balance!')
125+
add__line()
126+
127+
def return_all_blocks():
128+
print('---Outputting all blocks---')
129+
130+
for block in my_blockchain:
131+
print(f'Outputting block: {block}')
132+
add__line()
133+
134+
def get_balance(participant):
135+
sent_transactions = [[tx['amount'] for tx in block['transactions'] if tx['sender'] == participant] for block in my_blockchain]
136+
recieved_transactions = [[tx['amount'] for tx in block['transactions'] if tx['recipient'] == participant] for block in my_blockchain]
137+
open_sent_transactions = [tx['amount'] for tx in open_transactions if tx['sender'] == participant]
138+
139+
sent_transactions.append(open_sent_transactions)
140+
sent_amounts = functools.reduce(lambda tx_sum, tx_amt: tx_sum + sum(tx_amt) if len(tx_amt) > 0 else tx_amt + 0, sent_transactions, 0)
141+
142+
recieved_amounts = functools.reduce(lambda tx_sum, tx_amt: tx_sum + sum(tx_amt) if len(tx_amt) > 0 else tx_amt + 0, recieved_transactions, 0)
143+
144+
return recieved_amounts - sent_amounts
145+
146+
def verify_chain():
147+
""" The function helps to verify the integrity of the blockchain by checking if each block's previous hash matches the hash of the previous block. """
148+
for (index, block) in enumerate(my_blockchain):
149+
if index == 0:
150+
continue
151+
if block['previous_hash'] != hash_block(my_blockchain[index - 1]):
152+
return False
153+
# You are cheking all the transactions except the last one because the last one is the mining reward transaction
154+
if not valid_proof(block['transactions'][:-1], block['previous_hash'], block['proof']):
155+
print('Proof of work is invalid')
156+
return False
157+
return True
158+
159+
def verify_transactions():
160+
""" The function verifies all open transactions to ensure they are valid. """
161+
return all([tx for tx in open_transactions if not verify_transaction(tx)])
162+
163+
while waiting_for_input:
164+
generate_options_menu()
165+
166+
user_choice = get_user_input()
167+
168+
if user_choice == '1':
169+
tx_input_data = get_transaction_value()
170+
recipient, amount = tx_input_data
171+
add_transaction(owner, recipient, amount)
172+
elif user_choice == '2':
173+
if mine_block():
174+
open_transactions = []
175+
elif user_choice == '3':
176+
return_all_blocks()
177+
elif user_choice == '4':
178+
print(participants)
179+
elif user_choice == 'q':
180+
waiting_for_input = False
181+
elif user_choice == 'h':
182+
if len(my_blockchain) >= 1:
183+
my_blockchain[0] = [2.0]
184+
else:
185+
print('Invalid input, please choose a valid option')
186+
if not verify_chain():
187+
print('Invalid blockchain!')
188+
waiting_for_input = False
189+
print(f"Balance of {owner}: {get_balance(owner)}")
190+
else:
191+
print('User left!')
192+
193+
add__line()
194+
print('Done!')

5-standard-library/exercise.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
def add__line():
2+
print('--------------------')
3+
4+
# 1) Import the random function and generate both a random number between 0 and 1 as well as a random number between 1 and 10.
5+
import random
6+
7+
def generate_random_number():
8+
first_random_number = random.randrange(0, 10)
9+
second_random_number = random.randint(1, 10)
10+
return first_random_number, second_random_number
11+
12+
print(generate_random_number())
13+
print(generate_random_number())
14+
print(generate_random_number())
15+
add__line()
16+
17+
# 2) Use the datetime library together with the random number to generate a random, unique value.
18+
import datetime
19+
20+
def generate_unique_value_from_datetime():
21+
current_date_time = datetime.datetime.now()
22+
random_number = random.randint(1, 1000)
23+
unique_value = f"{current_date_time.year}{current_date_time.month}{current_date_time.day}{current_date_time.hour}{current_date_time.minute}{current_date_time.second}{random_number}"
24+
return unique_value
25+
26+
print(generate_unique_value_from_datetime())
27+
print(generate_unique_value_from_datetime())
28+
print(generate_unique_value_from_datetime())
29+
add__line()

5-standard-library/theory.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# ---IMPORT---
2+
3+
# Importing modules are similar to how is implemented in javascript
4+
# For example, if you have a file named random.py you can import it like this:
5+
# import random
6+
# You can also import it with an alias like this:
7+
# import random as r
8+
# Or you can import specific functions (like randint and shuffle) from a module like this:
9+
# from random import randint, shuffle

5-standard-library/utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Hashing functions are provided by the hashlib module
2+
import hashlib
3+
# The json module allows us to convert python objects into json strings (including structures such as lists and dictionaries)
4+
import json
5+
6+
def hash_string_256(string):
7+
""" Hash a string using sha256 """
8+
return hashlib.sha256(string).hexdigest()
9+
10+
def hash_block(block):
11+
""" Hash a block using its strucutre as base """
12+
# On this case, first we are going to convert the block into a json string
13+
# Then we are going to encode it to bytes with the enconde function
14+
encoded_block = json.dumps(block, sort_keys=True).encode()
15+
# And at last, we are going to return the hashed block using sha256
16+
# but converted into a hexadecimal string (for easier reading)
17+
return hash_string_256(encoded_block)

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,14 @@ cd python-practice
3030
```
3131

3232
## How to run it
33+
Each folder might have the following files:
34+
- `theory.py` for concepts explanation.
35+
- `blockchain.py` for improvements in course's main exercise.
36+
- `exercise.py` related to subject's learned lessons.
37+
3338
To run any specific exercise, execute the following command in the project´s folder:
3439
```python
40+
cd folder_name
3541
python3 exercise.py # Or any .py file
3642
```
3743

@@ -65,6 +71,9 @@ python3 exercise.py # Or any .py file
6571
- How to use string interpolation with the `f` method.
6672
- How to map lists with map method and `lambda functions`.
6773
- How to summarize data with `reduce` method.
74+
- Standard Library (`5-standard-library` folder)
75+
- Understanding the ways to import built-in functions from [Python Standard Library](https://docs.python.org/3/library/index.html).
76+
- Learn how to split functions in other files and import them.
6877

6978
## Other practice repos
7079
| Node | React | Angular | GraphQL | HTML & CSS | Styling | Typescript | NextJs | Docker |

0 commit comments

Comments
 (0)