-
Notifications
You must be signed in to change notification settings - Fork 0
/
blockchain.py
202 lines (185 loc) · 7.29 KB
/
blockchain.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
# Imports
import json
from utils.hash_util import find_hash
from proof_of_work import verify_proof_of_work, proof_of_work
from block import Block
from transaction import Transaction
from wallet import Wallet
class Blockchain:
def __init__(self, host_id):
self.__blockchain = []
self.__open_transactions = []
self.__MINER_REWARD = 200
self.participants = {"miner"}
self.GENESIS_BLOCK = Block(
index=0, previous_hash="", transactions=[], nonce="17", timestamp=0
)
self.fetch_data()
self.hosting_node = host_id
def get_blockchain(self):
"""
Return private blockchain
"""
return self.__blockchain[:]
def get_open_transactions(self):
"""
Return private open_transactions
"""
return self.__open_transactions[:]
def fetch_data(self):
"""
Fetches data upon start from data.txt file
"""
try:
with open("data.txt", mode="r") as f:
file_content = f.readlines()
# Remove \n
recovered_blockchain = json.loads(file_content[0][:-1])
for block in recovered_blockchain:
block_transactions = [
Transaction(
tx["sender"], tx["recipient"], tx["amount"], tx["sig"]
)
for tx in block["transactions"]
]
recovered_block = Block(
block["index"],
block["previous_hash"],
block_transactions,
block["nonce"],
block["timestamp"],
)
self.__blockchain.append(recovered_block)
recovered_open_transactions = json.loads(file_content[1])
self.__open_transactions = [
Transaction(tx["sender"], tx["recipient"], tx["amount"], tx["sig"])
for tx in recovered_open_transactions
]
except:
# File not found
self.__blockchain = [self.GENESIS_BLOCK]
self.__open_transactions = []
def save_data(self):
"""
Saves blockchain & open_transactions to a text file
"""
with open("data.txt", mode="w") as f:
saveable_blockchain = [block.__dict__.copy() for block in self.__blockchain]
for block in saveable_blockchain:
block["transactions"] = [tx.__dict__ for tx in block["transactions"]]
saveable_open_transactions = [
tx.__dict__ for tx in self.__open_transactions
]
f.write(json.dumps(saveable_blockchain))
f.write("\n")
f.write(json.dumps(saveable_open_transactions))
def get_last_blockchain_value(self):
"""
Returns the last value of the current blockchain
"""
if len(self.__blockchain) < 1:
return None
return self.__blockchain[-1]
def verify_transaction(self, transaction):
"""
Verifies if transaction is possible with sender's wallet balance
"""
if self.get_balance(transaction.sender) >= transaction.amount:
return True
print("💵❌ Insufficient funds!")
return False
def add_transaction(self, sender, recipient, amount, sig):
"""
Adds a transaction to open_transactions list
Arguments:
sender: address of sender
recipient: address of recipient
amount: amount of money to send
sig: digital signature
"""
if not self.hosting_node:
return False
new_transaction = Transaction(sender, recipient, amount, sig)
if not Wallet.verify_signature(new_transaction):
print("❌ Transaction signature could not be verified!")
return False
if not self.verify_transaction(new_transaction):
print("❌ Transaction verification failed")
return False
self.participants.add(sender)
self.participants.add(recipient)
self.__open_transactions.append(new_transaction)
self.save_data()
return True
def mine_block(self):
"""
Creates a new block, verifies proof of work, rewards miners and adds the block to the blockchain
"""
if not self.hosting_node:
return False
previous_block_hash = find_hash(self.__blockchain[-1])
reward_transaction = Transaction(
sender="MINING",
recipient=self.hosting_node,
amount=self.__MINER_REWARD,
sig="REWARD",
)
for transaction in self.__open_transactions:
if not Wallet.verify_signature(transaction):
print(
"❌⛏ Transaction signature in open_transactions could not be verified!"
)
return False
open_transactions_copy = self.__open_transactions[:]
open_transactions_copy.append(reward_transaction)
print("Started proof of work")
proof = proof_of_work(open_transactions_copy, previous_block_hash)
print("Finished proof of work with proof {}".format(proof))
new_block = Block(
index=len(self.__blockchain),
previous_hash=previous_block_hash,
transactions=open_transactions_copy,
nonce=proof,
)
self.__blockchain.append(new_block)
self.__open_transactions = []
self.save_data()
return True
def get_balance(self, participant):
"""
Gets balance of a participant from existing transactions on the blockchain
"""
participant_transactions = []
balance = 0.0
for block in self.__blockchain:
for transaction in block.transactions:
if transaction.recipient == participant:
balance += transaction.amount
participant_transactions.append(transaction)
if transaction.sender == participant:
balance -= transaction.amount
participant_transactions.append(transaction)
for transaction in self.__open_transactions:
if Wallet.verify_signature(transaction):
if transaction.recipient == participant:
balance += transaction.amount
participant_transactions.append(transaction)
if transaction.sender == participant:
balance -= transaction.amount
participant_transactions.append(transaction)
return balance
def verify_chain(self):
"""
Verifies if the blockchain is still valid by (a) checking previous_hash for each block (b) Verifying PoW for each block
"""
prev_hash = find_hash(self.GENESIS_BLOCK)
for block in self.__blockchain:
if block.index == 0:
continue
if block.previous_hash != prev_hash:
return False
if not verify_proof_of_work(block.transactions, prev_hash, block.nonce):
print("❌🧬 Chain couldn't be verifed!")
return False
prev_hash = find_hash(block)
return True