-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathnode.py
More file actions
246 lines (216 loc) · 8.41 KB
/
Copy pathnode.py
File metadata and controls
246 lines (216 loc) · 8.41 KB
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
238
239
240
241
242
243
244
245
246
import socket
from threading import Thread
import json
from blockchain import *
import random
MAGIC_PORT = 51412
HARDCODED_PEERS = ['25.100.101.237']
class Node:
def __init__(self, privKey = None, peers = [], chain = None):
self.privKey = privKey or PrivKeyWrapper(rsa.newkeys(512)[1]) # generate new keys if not supplied
self.pubKey = PubKeyWrapper(self.privKey.__dict__) # a PrivKey contains info of a PubKey
self.chain = chain
self.peers = peers
self.peerSocks = {}
self.pendingTxs = []
for peer in HARDCODED_PEERS:
if not peer in self.peers:
self.peers.append(peer)
self.listener = None
self.mining = False
self.debug = False
# Factory method to load node from json file
@staticmethod
def loadFromFile(path):
f = open(path, 'r')
obj = json.loads(f.read())
f.close()
peers = obj['peers']
privKey = PrivKeyWrapper(obj['privKey'])
if obj['chain'] != 'null' and obj['chain']:
chain = Blockchain.fromJSON(obj['chain'])
else:
chain = None
return Node(privKey, peers, chain)
# Write json representation to a file
def saveToFile(self, path):
obj = {
"chain": self.chain.toJSON() if self.chain else None,
"peers": self.peers,
"privKey": self.privKey # todo: don't store this as plaintext
}
f = open(path, 'w')
f.write(json.dumps(obj, default=lambda o:o.__dict__))
f.close()
def give(self, address, amt):
if amt > self.balance():
print("Insufficient balance")
return
myOuts = [txOut for txOut in self.chain.pool.txOuts if txOut.address.equals(self.pubKey)]
toGive = amt
consumed = []
created = [TxOut(address, amt)]
for out in myOuts:
consumed.append(TxIn(out.txHash, out.idx))
if out.value >= toGive:
created.append( TxOut(self.pubKey, out.value - toGive) )
toGive -= out.value
if toGive <= 0:
break
tx = Transaction(consumed, created)
for i in range(len(consumed)):
tx.sign(self.privKey.use(), i)
self.sendToPeers({
"type": 'TRANSACTION',
"data": tx.toJSON()
})
# Mine until stopMining() called
def mine(self):
if not self.chain:
print("No chain to mine on!")
return
if self.mining:
print("Already mining!")
return
self.mining = True
def m():
attempts = 0
while self.mining:
nonce = random.randint(0,10**10)
attempts += 1
childDiff = self.chain.nextDifficulty()
attemptBlock = Block(self.chain.blocks[-1].hash(), self.pubKey, self.pendingTxs, nonce, childDiff)
if attemptBlock.satisfiedDifficulty() >= childDiff:
print("Mined block in {} attempts".format(attempts))
self.chain.addBlock(attemptBlock)
self.shareChain()
self.mining = False
Thread(target = m).start()
def stopMining(self):
self.mining = False
# Shares chain with all peers BUT IT SHOULD ONLY SHARE WITH ONE INSTEAD
def shareChain(self, recipient='all'):
chain = self.chain.toJSON()
self.sendToPeers({
"type": "CHAIN",
"data": chain
}, recipient)
def ping(self, recipient='all'):
self.sendToPeers({
"type": "PING"
}, recipient)
def requestChain(self):
self.sendToPeers({
"type": "REQUEST_CHAIN"
})
def sendMsg(self, msg):
self.sendToPeers({
"type": "MESSAGE",
"data": msg
})
def balance(self):
return sum(txOut.value for txOut in self.chain.pool.txOuts if txOut.address.equals(self.pubKey))
def handleRequest(self, request, source):
if request['type'] == "REQUEST_CHAIN":
self.shareChain(source)
elif request['type'] == "CHAIN":
candidate = Blockchain.fromJSON(request['data'])
if len(candidate.blocks) > len(self.chain.blocks): # todo: and candidate is valid
self.chain = candidate
elif request['type'] == "TRANSACTION":
candidate = Transaction.fromJSON(request['data'])
try:
self.chain.pool.verifyTx(candidate)
except Exception as e:
print("Invalid transaction!")
if self.debug:
raise e
for tx in self.pendingTxs:
if candidate.equals(tx):
if self.debug:
print("Duplicate transaction proposed!")
return
self.pendingTxs.append(candidate)
self.sendToPeers(request)
elif request['type'] == "PING":
print("Ping from {}".format(source))
else:
print("Unknown request type!")
return
# Run once after intialization to connect node to network
def connect(self):
# Expose magic port to new connections
self.listener = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.listener.bind(('',MAGIC_PORT))
Thread(target = self.listen).start()
# Connect to peers
for peer in self.peers:
self.connectToPeer(peer)
# Listen on the magic port
def listen(self):
while not self.listener._closed:
try:
# Listen for a new connection
self.listener.listen(1)
(clientname,address)=self.listener.accept()
# Upon connection, create a thread to receive data
Thread(target=self.receiveContinually, args=[clientname, address]).start()
print("Received connection from {}, attempting connect back.".format(address))
# Adds connection to list of known peers
self.addPeer(address[0])
except OSError as e:
if e.winerror == 10038:
# Expected error upon socket close in another thread
# Unavoidable w/o a janky hack
pass
else:
raise e
def receiveContinually(self, clientname, address):
while 1:
try:
chunk = b''
while not b'<END>' in chunk:
chunk += clientname.recv(4096)
chunk = chunk.replace(b'<END>', b'')
if len(chunk):
if self.debug:
print("Received:")
print(chunk)
if chunk != b'null':
self.handleRequest(json.loads(chunk), address[0])
except ConnectionResetError as e:
if e.errno==54:
break
else:
raise e
def sendToPeers(self, request, recipient='all'):
data = (json.dumps(request) + "<END>").encode("utf-8")
for peer in self.peerSocks:
if recipient == 'all' or recipient == peer:
self.peerSocks[peer].send(data)
if self.debug:
print("Sent to {}:".format(peer))
print(data)
def addPeer(self, peer):
if not peer in self.peers:
self.peers.append(peer)
if peer in self.peerSocks:
try:
self.peerSocks[peer].send('null<END>'.encode('utf-8'))
return
except:
pass
self.connectToPeer(peer)
def connectToPeer(self, peer):
def t(peer):
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
try:
sock.connect((peer, MAGIC_PORT))
self.peerSocks[peer] = sock
except TimeoutError as e:
pass
except ConnectionRefusedError as e:
if e.errno == 10061:
pass
Thread(target=t, args=[peer]).start()