1+ import functools
2+ import json
3+ # CLASS IMPORTS
4+ # Have in mind that in order to correctly import a class located in a folder
5+ # You have to call first the folder (or folders in order) and lastly, the file
6+ # In this case, I make the import of the Class at the end to avoid verbosity
7+ from classes .block import Block
8+ from classes .transaction import Transaction
9+ from classes .verification import Verification
10+ # OTHER IMPORTS
11+ from utils import hash_block , add__line
12+
13+ MINING_REWARD = 10
14+
15+ class Blockchain :
16+ def __init__ (self , node_hosting_id ):
17+ genesis_block = Block (0 , '' , [], 100 )
18+ self .hosting_id = node_hosting_id
19+ # Getters and setters are the ways to get and set class attributes without accesing them directly (check encapsulation concept)
20+ # To implement these methods, we can use one of two ways
21+ # The first one is to create private attributes (starting with double underscore)
22+ # These two attributes below will be set as private to be accesible only inside the class
23+ # self.__chain = [genesis_block]
24+ # self.__open_transactions = []
25+ # The second one is creating the attributes and add specific methods for its handling
26+ self .chain = [genesis_block ]
27+ self .open_transactions = []
28+ # When you create an instance of this class, it will start the load data process
29+ self .load_data ()
30+
31+ # This is a get method using the first way of encapsulation
32+ # def get_chain(self):
33+ # return self.__chain[:]
34+
35+ # This is a get method using the first way of encapsulation
36+ # def get_open_transactions(self):
37+ # return self.__open_transactions[:]
38+
39+ # This is a get method using the second way of encapsulation
40+ # The first difference is adding a decorator to indicate is a property accessor method
41+ # The second one is its method name, is the same as the property
42+ # And at last, it returns the private property (with double underscore) even if it has been asigned as a public property
43+ @property
44+ def chain (self ):
45+ return self .__chain [:]
46+
47+ @property
48+ def open_transactions (self ):
49+ return self .__open_transactions [:]
50+
51+ # And this is the setter method, it also starts with a decorator, but related to the property
52+ # The method includes the self reference (to the class) and a value argument that will after be used to set the private attribute value
53+ @chain .setter
54+ def chain (self , val ):
55+ self .__chain = val
56+
57+ @open_transactions .setter
58+ def open_transactions (self , val ):
59+ self .__open_transactions = val
60+
61+
62+ def load_data (self ):
63+ try :
64+ with open ('blockchain.txt' , mode = 'r' ) as f :
65+ file_content = f .readlines ()
66+
67+ blockchain = json .loads (file_content [0 ])
68+ updated_blockchain = []
69+ updated_transactions = []
70+
71+ for current_block in blockchain :
72+ block_transactions = [Transaction (tx ['sender' ], tx ['recipient' ], tx ['amount' ]) for tx in current_block .transactions ]
73+ updated_block = Block (
74+ current_block .proof ,
75+ current_block .previous_hash ,
76+ block_transactions ,
77+ current_block .proof )
78+ updated_blockchain .append (updated_block )
79+
80+ for tx in current_block .transactions :
81+ updated_transaction = Transaction (tx ['sender' ], tx ['recipient' ], tx ['amount' ])
82+ updated_transactions .append (updated_transaction )
83+
84+ # Before learning about properties, the variable was been updated like this:
85+ # self.__chain = updated_blockchain
86+ # Now, updating class attribute in this way (instead the way before), you
87+ # are accessing the property instead the variable
88+ self .chain = updated_blockchain
89+ # This is the recommended alternative due is using specific features for class encapsulation
90+ self .open_transactions = updated_transactions
91+ except (IOError , IndexError ):
92+ # Exception logic has been handled in class initializer
93+ pass
94+ finally :
95+ print ('Cleanup' )
96+
97+ def save_data (self ):
98+ # To create the chain in a json-accepted format, we have to parse the list as a dictionary list
99+ savable_chain = [block .__dict__ for block in [Block (block_el .index , block_el .previous_hash , [tx .__dict__ for tx in block_el .transactions ], block_el .proof , block_el .timestamp ) for block_el in self .__chain ]]
100+ savable_transactions = [tx .__dict__ for tx in self .__open_transactions ]
101+
102+ with open ('blockchain.txt' , mode = 'w' ) as f :
103+ f .write (json .dumps (savable_chain ))
104+ f .write ('\n ' )
105+ f .write (json .dumps (savable_transactions ))
106+
107+ def get_balance (self , participant ):
108+ # There are several cases on this exercise where the variable access through private
109+ # calling istead the property is recommended because of this example
110+ # self.chain # you get access to a copy of the chain, only recommended for instances
111+ # self.__chain # you get access to the chain created, recommended for internal class usage in other methods
112+ # The reason of this differnce is to avoid using copies of the data inside the class
113+ # that could not be on sync with the latest data
114+ # But once you are accessing through an instance, is required to access though the property (getter) and not the private attribute
115+ sent_transactions = [[tx .amount for tx in block .transactions if tx .sender == participant ] for block in self .__chain ]
116+ recieved_transactions = [[tx .amount for tx in block .transactions if tx .recipient == participant ] for block in self .__chain ]
117+ open_sent_transactions = [tx .amount for tx in self .__open_transactions if tx .sender == participant ]
118+
119+ sent_transactions .append (open_sent_transactions )
120+ sent_amounts = functools .reduce (lambda tx_sum , tx_amt : tx_sum + sum (tx_amt ), sent_transactions , 0 )
121+ recieved_amounts = functools .reduce (lambda tx_sum , tx_amt : tx_sum + sum (tx_amt ), recieved_transactions , 0 )
122+
123+ return recieved_amounts - sent_amounts
124+
125+ def take_last_blockchain_value (self ):
126+ if len (self .__chain ) < 1 :
127+ return None
128+
129+ return self .__chain [- 1 ]
130+
131+ def add_transaction (self , sender , recipient , amount = 1 ):
132+ """
133+ Add a new transaction to the list of open transactions (which will be added to the next mined block)
134+
135+ Arguments:
136+ :sender: The sender of the coins.
137+ :recipient: The recipient of the coins.
138+ :amount: The amount of the transaction.
139+ """
140+ new_transaction = Transaction (sender , recipient , amount )
141+
142+ # Using class and static methods, those can be called without need to instanciate it
143+ if Verification .verify_transaction (new_transaction , self .get_balance ):
144+ self .__open_transactions .append (new_transaction )
145+ self .save_data ()
146+ else :
147+ print ('Transaction failed! Not enough balance!' )
148+ add__line ()
149+
150+ def mine_block (self ):
151+ last_block = self .__chain [- 1 ]
152+ hashed_block = hash_block (last_block )
153+ proof_of_work_value = self .proof_of_work ()
154+
155+ reward_transaction = Transaction ('MINING' , self .hosting_id , MINING_REWARD )
156+
157+ transactions = self .__open_transactions .copy ()
158+ transactions .append (reward_transaction )
159+ new_block = Block (
160+ len (self .__chain ),
161+ hashed_block ,
162+ transactions ,
163+ proof_of_work_value
164+ )
165+ self .__chain .append (new_block )
166+ self .__open_transactions = []
167+ self .save_data ()
168+ print ('Block added!' )
169+ add__line ()
170+ return True
171+
172+ def proof_of_work (self ):
173+ last_block = self .__chain [- 1 ]
174+ hash_last_block = hash_block (last_block )
175+ proof = 0
176+ # Using class and static methods, those can be called without need to instanciate it
177+ while not Verification .valid_proof (self .__open_transactions , hash_last_block , proof ):
178+ proof += 1
179+ return proof
0 commit comments