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

Commit 3dc77d2

Browse files
authored
feat: Exercise #6. File Management
BREAKING CHANGE: Exercise completed.
2 parents db8f853 + 43e24a6 commit 3dc77d2

6 files changed

Lines changed: 394 additions & 1 deletion

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,5 +208,6 @@ __marimo__/
208208

209209
# dependencies
210210
**/node_modules
211+
.DS_Store
211212

212-
.DS_Store
213+
*.txt

6-file-management/blockchain.py

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# A built in module that provides functions from python standard library
2+
import functools
3+
import collections
4+
import json
5+
6+
from utils import hash_block, hash_string_256
7+
8+
MINING_REWARD = 10
9+
genesis_block = {
10+
'previous_hash': '',
11+
'index': 0,
12+
'transactions': [],
13+
'proof': 100
14+
}
15+
my_blockchain = [genesis_block]
16+
open_transactions = []
17+
waiting_for_input = True
18+
owner = 'Nicolas'
19+
participants = set([owner])
20+
21+
def add__line():
22+
print('------------------------------')
23+
24+
def generate_options_menu():
25+
add__line()
26+
print('Please choose an option:')
27+
print('1: Add a new transaction')
28+
print('2: Mine a new block')
29+
print('3: Output all blockchain blocks')
30+
print('4: Output all participants')
31+
print('h: Manipulate blockchain')
32+
print('q: Quit')
33+
add__line()
34+
35+
def get_user_input():
36+
user_input = input('Please enter your selection: ')
37+
add__line()
38+
return user_input
39+
40+
def valid_proof(transactions, last_hash, proof):
41+
guess = (str(transactions) + str(last_hash) + str(proof)).encode()
42+
guess_hash = hash_string_256(guess)
43+
# If the hash starts with 2 leading zeros, we consider it a valid proof
44+
return guess_hash[0:2] == '00'
45+
46+
def proof_of_work():
47+
last_block = my_blockchain[-1]
48+
hash_last_block = hash_block(last_block)
49+
proof = 0
50+
while not valid_proof(open_transactions, hash_last_block, proof):
51+
proof += 1
52+
return proof
53+
54+
55+
56+
def mine_block():
57+
last_block = my_blockchain[-1]
58+
hashed_block = hash_block(last_block)
59+
proof_of_work_value = proof_of_work()
60+
61+
# Old way to create a mining reward transaction, it will be replaced by an OrderedDict to keep the order of the elements
62+
# reward_transaction = {
63+
# 'sender': 'MINING',
64+
# 'recipient': owner,
65+
# 'amount': MINING_REWARD
66+
# }
67+
reward_transaction = collections.OrderedDict([('sender', 'MINING'), ('recipient', owner), ('amount', MINING_REWARD)])
68+
69+
block = {
70+
'previous_hash': hashed_block,
71+
'index': len(my_blockchain),
72+
'transactions': [open_transactions, reward_transaction],
73+
'proof': proof_of_work_value
74+
}
75+
76+
my_blockchain.append(block)
77+
print('Block added!')
78+
add__line()
79+
return True
80+
81+
def get_transaction_value():
82+
""" Returns the input of the user (a new transaction amount and its recipient) as a tuple """
83+
tx_recipient_input = input('Please enter the recipient of the transaction: ')
84+
tx_amount_input = float(input('Please enter your transaction input: '))
85+
add__line()
86+
87+
return tx_recipient_input, tx_amount_input
88+
89+
def take_last_blockchain_value():
90+
if len(my_blockchain) < 1:
91+
return None
92+
93+
return my_blockchain[-1]
94+
95+
def verify_transaction(transaction):
96+
sender_balance = get_balance(transaction['sender'])
97+
98+
if sender_balance >= transaction['amount']:
99+
return True
100+
return False
101+
102+
def add_transaction(sender, recipient, amount=1):
103+
"""
104+
Add a new transaction to the list of open transactions (which will be added to the next mined block)
105+
106+
Arguments:
107+
:sender: The sender of the coins.
108+
:recipient: The recipient of the coins.
109+
:amount: The amount of the transaction.
110+
"""
111+
112+
# An old way to create a transaction, it will be replaced by an OrderedDict to keep the order of the elements
113+
# new_transaction = {
114+
# 'sender': sender,
115+
# 'recipient': recipient,
116+
# 'amount': amount
117+
# }
118+
new_transaction = collections.OrderedDict([('sender', sender), ('recipient', recipient), ('amount', amount)])
119+
120+
if verify_transaction(new_transaction):
121+
open_transactions.append(new_transaction)
122+
participants.add(sender)
123+
participants.add(recipient)
124+
save_data()
125+
else:
126+
print('Transaction failed! Not enough balance!')
127+
add__line()
128+
129+
def return_all_blocks():
130+
print('---Outputting all blocks---')
131+
132+
for block in my_blockchain:
133+
print(f'Outputting block: {block}')
134+
add__line()
135+
136+
def get_balance(participant):
137+
sent_transactions = [[tx['amount'] for tx in block['transactions'] if tx['sender'] == participant] for block in my_blockchain]
138+
recieved_transactions = [[tx['amount'] for tx in block['transactions'] if tx['recipient'] == participant] for block in my_blockchain]
139+
open_sent_transactions = [tx['amount'] for tx in open_transactions if tx['sender'] == participant]
140+
141+
sent_transactions.append(open_sent_transactions)
142+
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)
143+
144+
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)
145+
146+
return recieved_amounts - sent_amounts
147+
148+
def verify_chain():
149+
""" 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. """
150+
for (index, block) in enumerate(my_blockchain):
151+
if index == 0:
152+
continue
153+
if block['previous_hash'] != hash_block(my_blockchain[index - 1]):
154+
return False
155+
# You are cheking all the transactions except the last one because the last one is the mining reward transaction
156+
if not valid_proof(block['transactions'][:-1], block['previous_hash'], block['proof']):
157+
print('Proof of work is invalid')
158+
return False
159+
return True
160+
161+
def verify_transactions():
162+
""" The function verifies all open transactions to ensure they are valid. """
163+
return all([tx for tx in open_transactions if not verify_transaction(tx)])
164+
165+
def load_data():
166+
with open('blockchain.txt', mode='r') as f:
167+
file_content = f.readlines()
168+
# This adds a reference of the mentioned varables to be handled
169+
global my_blockchain
170+
global open_transactions
171+
# Have in mind that the data you are returning is in string format, does not care if is a list or dictionary
172+
# my_blockchain = file_content[0]
173+
# To convert the string data into a list or dictionary you can use the json module
174+
my_blockchain = json.loads(file_content[0])
175+
updated_blockchain = []
176+
updated_transactions = []
177+
178+
for block in my_blockchain:
179+
updated_block = {
180+
'previous_hash': block['previous_hash'],
181+
'proof': block['proof'],
182+
'index': block['index'],
183+
'transactions': [collections.OrderedDict(
184+
[('sender', tx['sender']), ('recipient', tx['recipient']), ('amount', tx['amount'])]) for tx in block['transactions']],
185+
}
186+
updated_blockchain.append(updated_block)
187+
188+
for tx in block['transactions']:
189+
updated_transaction = collections.OrderedDict(
190+
[('sender', tx['sender']), ('recipient', tx['recipient']), ('amount', tx['amount'])])
191+
updated_transactions.append(updated_transaction)
192+
193+
my_blockchain = updated_blockchain
194+
open_transactions = updated_transactions
195+
196+
def save_data():
197+
with open('blockchain.txt', mode='w') as f:
198+
# To save the data in another format rather than string, you can use the json module to convert it
199+
f.write(json.dumps(my_blockchain))
200+
f.write('\n')
201+
f.write(json.dumps(open_transactions))
202+
203+
while waiting_for_input:
204+
generate_options_menu()
205+
206+
user_choice = get_user_input()
207+
208+
if user_choice == '1':
209+
tx_input_data = get_transaction_value()
210+
recipient, amount = tx_input_data
211+
add_transaction(owner, recipient, amount)
212+
elif user_choice == '2':
213+
if mine_block():
214+
open_transactions = []
215+
save_data()
216+
elif user_choice == '3':
217+
return_all_blocks()
218+
elif user_choice == '4':
219+
print(participants)
220+
elif user_choice == 'q':
221+
waiting_for_input = False
222+
elif user_choice == 'h':
223+
if len(my_blockchain) >= 1:
224+
my_blockchain[0] = [2.0]
225+
else:
226+
print('Invalid input, please choose a valid option')
227+
if not verify_chain():
228+
print('Invalid blockchain!')
229+
waiting_for_input = False
230+
print(f"Balance of {owner}: {get_balance(owner)}")
231+
else:
232+
print('User left!')
233+
234+
add__line()
235+
print('Done!')

6-file-management/exercise.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
def add__line():
2+
print('--------------------')
3+
4+
# 1) Write a short Python script which queries the user for input (infinite loop with exit possibility) and writes the input to a file.
5+
6+
# waiting_for_input = True
7+
8+
# while waiting_for_input:
9+
# add__line()
10+
# print('Please choose an option:')
11+
# print('1: Add a new phrase')
12+
# print('q: Quit')
13+
# add__line()
14+
15+
# user_choice = input('Please enter your selection: ')
16+
# add__line()
17+
18+
# if user_choice == '1':
19+
# tx_input_data = input('Please enter the new phrase: ')
20+
# with open('exercise.txt', mode='a') as f:
21+
# f.write(tx_input_data + '\n')
22+
# elif user_choice == 'q':
23+
# waiting_for_input = False
24+
# else:
25+
# print('Invalid input, please choose a valid option')
26+
# else:
27+
# print('User left!')
28+
29+
# 2) Add another option to your user interface: The user should be able to output the data stored in the file in the terminal.
30+
31+
# waiting_for_input = True
32+
33+
# while waiting_for_input:
34+
# add__line()
35+
# print('Please choose an option:')
36+
# print('1: Add a new phrase')
37+
# print('2: Show all phrases')
38+
# print('q: Quit')
39+
# add__line()
40+
41+
# user_choice = input('Please enter your selection: ')
42+
# add__line()
43+
44+
# if user_choice == '1':
45+
# tx_input_data = input('Please enter the new phrase: ')
46+
# with open('exercise.txt', mode='a') as f:
47+
# f.write(tx_input_data + '\n')
48+
# add__line()
49+
# elif user_choice == '2':
50+
# with open('exercise.txt', mode='r') as f:
51+
# line = f.readline()
52+
# while line:
53+
# print(line)
54+
# line = f.readline()
55+
# add__line()
56+
# print('Done reading the file.')
57+
# elif user_choice == 'q':
58+
# waiting_for_input = False
59+
# else:
60+
# print('Invalid input, please choose a valid option')
61+
# else:
62+
# print('User left!')
63+
64+
# 3) Store user input in a list (instead of directly adding it to the file) and write that list to the file – both with pickle and json.
65+
66+
waiting_for_input = True
67+
68+
while waiting_for_input:
69+
user_inputs = []
70+
71+
add__line()
72+
print('Please choose an option:')
73+
print('1: Add a new phrase')
74+
print('2: Show all phrases')
75+
print('q: Quit')
76+
add__line()
77+
78+
user_choice = input('Please enter your selection: ')
79+
add__line()
80+
81+
if user_choice == '1':
82+
tx_input_data = input('Please enter the new phrase: ')
83+
user_inputs.append(tx_input_data)
84+
elif user_choice == '2':
85+
print('Stored phrases:')
86+
for phrase in user_inputs:
87+
print(phrase)
88+
add__line()
89+
print('Reading from the file:')
90+
with open('exercise.txt', mode='r') as f:
91+
line = f.readline()
92+
while line:
93+
print(line)
94+
line = f.readline()
95+
add__line()
96+
print('Done reading the file.')
97+
elif user_choice == 'q':
98+
with open('exercise.txt', mode='a') as f:
99+
f.write(tx_input_data + '\n')
100+
add__line()
101+
waiting_for_input = False
102+
else:
103+
print('Invalid input, please choose a valid option')
104+
else:
105+
print('User left!')
106+
107+
# 4) Adjust the logic to load the file content to work with pickled/ json data.

6-file-management/theory.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Open is a method to work on a file using python
2+
# On its arguments, the second one is the mode to open the file
3+
# r, read
4+
# w, write
5+
# r+, read and write
6+
# x, create and if files exists it will return an error
7+
# a, append the text to the end of the file
8+
# b, binary mode
9+
demo_file = open('theory.txt', mode='a')
10+
# The \n string is a new line character to jump to the next line
11+
demo_file.write('This is a demo file.\n')
12+
demo_file.write('This is a demo file.\n')
13+
demo_file.write('This is a demo file.\n')
14+
demo_file.write('This is a demo file.\n')
15+
# ALWAYS close the file after working on it (to avoid memory leaks)
16+
demo_file.close()
17+
18+
19+
# This code adds a couple of interesting things
20+
# First, you use the open block statement to open the file and close it when its code block ends
21+
with open('theory.txt', mode='r') as f:
22+
# Second, you read the file line by line using the readline() method
23+
line = f.readline()
24+
# Then, you use a while loop to read each line until there are no more lines to read
25+
while line:
26+
print(line)
27+
line = f.readline()
28+
# And when the block ends, the file is closed automatically (no need to call close())
29+
print('Done reading the file.')

0 commit comments

Comments
 (0)