-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathblockchain.py
237 lines (203 loc) · 8.96 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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import functools
import hashlib
import json
import os.path
import pickle
import requests
from block import Block
from transaction import Transaction
from utils.verification import Verification
from utils.hash_utils import hash_string_256, hash_block
from wallet import Wallet
# The reward we give to miners (for creating a new block)
MINING_REWARD = 10
def file_exists(path):
return os.path.isfile(path)
class Blockchain:
# Initializing blockchain list
def __init__(self, public_key, node_id):
genesis_block = Block(0, '', [], 100, 0) # Starting block
self.chain = [genesis_block] # list of blocks
self.__open_transactions = [] # list of unhandled transactions
self.public_key = public_key
self.__peer_nodes = set()
self.node_id = node_id
self.resolve_conflicts = False
self.load_data()
@property
def chain(self):
return self.__chain[:]
@chain.setter
def chain(self, val):
self.__chain = val
def get_open_transactions(self):
return self.__open_transactions[:]
def load_data(self):
try:
with open('blockchain-{}.txt'.format(self.node_id), mode='rb') as f:
file_content = pickle.loads(f.read())
self.chain = file_content['chain']
self.__open_transactions = file_content['ot']
self.__peer_nodes = file_content['peer_nodes']
except IOError:
print('File not found')
def save_data(self):
try:
# Use pickle lib to store data in a binary format
with open('blockchain-{}.txt'.format(self.node_id), mode='wb') as file:
save_data = {
'chain': self.__chain,
'ot': self.__open_transactions,
"peer_nodes": self.__peer_nodes
}
file.write(pickle.dumps(save_data))
except IOError:
print('Saving failed')
def proof_of_work(self):
last_block = self.__chain[-1]
last_hash = hash_block(last_block)
proof = 0
while not Verification.valid_proof(self.__open_transactions, last_hash, proof):
proof += 1
return proof
def get_balance(self, sender=None):
if sender == None:
if self.public_key == None:
return None
participant = self.public_key
else:
participant = sender
# list comprehension
# get the list with amount of coins sent
tx_sender = [[tx.amount for tx in block.transactions if participant == tx.sender]
for block in self.__chain]
open_tx_sender = [tx.amount
for tx in self.__open_transactions if tx.sender == participant]
tx_sender.append(open_tx_sender)
# Calc total amount sent with reduce func
amount_sent = functools.reduce(
lambda tx_sum, tx_amount: tx_sum + sum(tx_amount) if len(tx_amount) > 0 else tx_sum + 0, tx_sender, 0)
# get the list with amount of coins recieved
tx_recipient = [[tx.amount for tx in block.transactions if participant == tx.recipient]
for block in self.__chain]
# Calc total amount recieved with reduce func
amount_recieved = functools.reduce(
lambda tx_sum, tx_amount: tx_sum + sum(tx_amount) if len(tx_amount) > 0 else tx_sum + 0, tx_recipient, 0)
balance = amount_recieved - amount_sent
return (amount_sent, amount_recieved, balance)
def get_last_blockchain_value(self):
""" Returns the last value of the current blockchain. """
if len(self.__chain) < 1:
return None
return self.__chain[-1]
def add_transaction(self, recipient, sender, signature, amount=1.0, is_receiving=False):
# if self.public_key == None:
# return False
transaction = Transaction(sender, recipient, signature, amount)
if Verification.verify_tx(transaction, self.get_balance):
self.__open_transactions.append(transaction)
self.save_data()
if not is_receiving:
for node in self.__peer_nodes:
url = 'http://{}/broadcast'.format(node)
try:
response = requests.post(
url, json={"sender": sender, "recipient": recipient, "amount": amount, "signature": signature})
if response.status_code == 400 or response.status_code == 500:
print("Couldn't add TX to peer_nodes")
return False
except requests.exceptions.ConnectionError:
continue
return True
return False
def mine_block(self):
if self.public_key == None:
return None
last_block = self.__chain[-1] # get last_block from blockchain list
hashed_block = hash_block(last_block)
proof = self.proof_of_work()
reward_tx = Transaction(
'MINING', self.public_key, '', MINING_REWARD)
# Copy trans. instead of mutating original open_tx.
copied_open_transactions = self.__open_transactions[:]
# Verify each transaction in block
for tx in copied_open_transactions:
if not Wallet.verify_transaction(tx):
return None
# Add reward transaction
copied_open_transactions.append(reward_tx)
# Create a block object
block = Block(len(self.__chain), hashed_block,
copied_open_transactions, proof)
# Add newly created block to blockchain
self.__chain.append(block)
self.__open_transactions = []
self.save_data()
for node in self.__peer_nodes:
url = 'http://{}/broadcast_block'.format(node)
converted_block = block.__dict__.copy()
converted_block['transactions'] = [
tx.__dict__ for tx in converted_block['transactions']]
try:
response = requests.post(
url, json={"block": converted_block})
if response.status_code == 400 or response.status_code == 500:
print("Couldn't add broadcasted block to blockchain")
if response.status_code == 409:
self.resolve_conflicts = True
except requests.exceptions.ConnectionError:
continue
return block
def add_block(self, block):
transactions = [Transaction(
tx['sender'], tx['recipient'], tx['signature'], tx['amount']) for tx in block['transactions']]
valid_prood = Verification.valid_proof(
transactions[:-1], block['previous_hash'], block['proof'])
hashes_match = hash_block(self.chain[-1]) == block['previous_hash']
if not valid_prood or not hashes_match:
return False
converted_block = Block(
block['index'], block['previous_hash'], transactions, block['proof'], block['timestamp'])
self.__chain.append(converted_block)
stored_txs = self.__open_transactions[:]
for incoming_tx in block['transactions']:
for open_tx in stored_txs:
if open_tx.sender == incoming_tx['sender'] and open_tx.recipient == incoming_tx['recipient'] and open_tx.amount == incoming_tx['amount'] and open_tx.signature == incoming_tx['signature']:
try:
self.__open_transactions.remove(open_tx)
except ValueError:
print('TX was already removed')
self.save_data()
return True
def add_peer_node(self, node):
self.__peer_nodes.add(node)
self.save_data()
def remove_peer_node(self, node):
self.__peer_nodes.discard(node)
self.save_data()
def get_peer_nodes(self):
return list(self.__peer_nodes)
def resolve(self):
winner_chain = self.chain
replace = False
for node in self.__peer_nodes:
url = 'http://{}/chain'.format(node)
try:
response = requests.get(url)
node_chain = response.json()
node_chain = [Block(block['index'], block(
'previos_hash'), [Transaction(
tx['sender'], tx['recipient'], tx['signature'], tx['amount']) for tx in block['transactions']], block['proof'], block['timestamp']) for block in node_chain]
node_chain_len = len(node_chain)
local_chain_len = len(winner_chain)
if node_chain_len > local_chain_len and Verification.verify_chain(node_chain):
winner_chain = node_chain
replace = True
except requests.exceptions.ConnectionError:
continue
self.resolve_conflicts = False
self.chain = winner_chain
if replace:
self.__open_transactions = []
self.save_data()
return replace