-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrest.py
307 lines (252 loc) · 9.87 KB
/
rest.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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
import requests
from flask import Flask, jsonify, request
from flask_cors import CORS
from werkzeug.contrib.cache import SimpleCache
from utils import create_logger
from node import Node
from transaction import Transaction
from block import Block
from blockchain import Blockchain
logger = create_logger('rest')
app = Flask(__name__)
CORS(app)
cache = SimpleCache(default_timeout=0)
# ------------------------------ Bootstrapping --------------------------------
'''
Context: Bootstrapper Node | Used from: Other Nodes (because of nbc CLI)
This route will only be used from the bootstrapper node. All other nodes will
POST it when initializing. It is used to keep track of the nodes that want to
connect. Once all of them POST, then the bootstrapper will notify everyone
'''
@app.route('/nodes', methods=['POST'])
def post_node():
counter = cache.get('counter')
node = cache.get('node')
data = request.get_json()
logger.info('New node connected: ' + data['address'] + '. ' +
'It will have ID {' + str(counter) + '}.')
# Add the new node to the bootstrapper's ring and update the counters
data['id'] = counter
node.register_node_to_ring(data)
cache.set('node', node)
cache.set('counter', counter + 1)
return jsonify('Bootstrap notified successfully'), 200
'''
Context: Bootstrapper Node | Called from: nbc CLI
Once all the nodes are up and have pinged the Bootstrapper, the CLI will make a
GET to this route in the Bootstrapper node. Then the BS node will broadcast to
everyone the final ring.
'''
@app.route('/broadcast-ring')
def get_broadcast_ring():
node = cache.get('node')
node.broadcast_ring()
cache.set('node', node)
logger.info('Bootstrapping: All nodes connected!')
logger.info([node['address'] for node in cache.get('node').ring])
return jsonify('Broadcasted the ring successfully'), 200
'''
Context: Non-Bootstrapper Nodes | Used from: Bootstrapper Node
The Bootstrapper will POST each node when everyone is connected.
The node must update its own ring with the one he got from the bootstrapper
and also update his id value
'''
@app.route('/connect', methods=['POST'])
def post_connect():
data = request.get_json()
ring = data['ring']
genesis_chain = data['genesis_chain']
node = cache.get('node')
for nd in ring:
if nd['address'] == node.address:
node.id = nd['id']
node.ring = ring
node.chain = Blockchain(json=True, **genesis_chain)
cache.set('node', node)
logger.info('Node successfully connected with ID: ' + str(node.id))
logger.info('Network ring: ' + str(node.ring))
return jsonify('OK'), 200
# ------------------------------- NoobCoin ------------------------------------
'''
Used by CLI to create and broadcast a new transaction
based on the receiver's id and the amount
'''
@app.route('/create-transaction', methods=['POST'])
def create_transaction():
args = request.get_json()
node = cache.get('node')
target_id = args['id']
value = args['value']
logger.info('** User wants me to send {} NBCs -> Node {}'.format(value, target_id))
t = node.create_transaction(target_id, value)
cache.set('node', node)
if t is not None:
node.broadcast_transaction(t) # hit /send-transaction endpoint n times
return jsonify('OK'), 200
else:
return jsonify('ERROR'), 404
'''
Used by nodes to notify each other of a transaction.
'''
@app.route('/send-transaction', methods=['POST'])
def send_transaction():
logger.info('* Heard a Transaction, adding it o buffer')
data = request.get_json()
node = cache.get('node')
transaction = Transaction(**data['transaction'])
if node.validate_transaction(transaction):
# update buffer
node.tx_buffer.append(transaction)
# update wallet if needed
if transaction.receiver_address == node.public_key: # get sent money
node.wallet.utxos.append(transaction.transaction_outputs[0])
if transaction.sender_address == node.public_key: # get change money
node.wallet.utxos.append(transaction.transaction_outputs[1])
cache.set('node', node)
logger.info('* Buffer has {} Transactions.'.format(len(node.tx_buffer)))
MINING = cache.get('MINING')
if (not MINING) and (len(node.tx_buffer) >= node.CAPACITY):
logger.info('Notify self, start Minning')
address = 'http://' + node.address + '/mine-block'
try:
requests.get(address, timeout=1)
logger.warning('******PING EXCEPTION NOT THROWN')
except Exception:
logger.warning('Succsffully notified the Minning endpoint')
return jsonify('OK'), 200
else:
logger.info('Transaction couldn\'t be validated')
cache.set('node', node)
return jsonify('INVTR'), 404
'''
Get notified to start mining
'''
@app.route('/mine-block')
def mineBlock():
node = cache.get('node')
MINING = cache.get('MINING')
while (not MINING) and (len(node.tx_buffer) >= node.CAPACITY):
# construct Block to mine
block_to_mine = Block(**{'previousHash': node.chain.get_last_block().hash})
# fill the block with transactions from the buffer
for t in range(node.CAPACITY):
block_to_mine.add_transaction(node.tx_buffer[t])
# calculate block hash
block_to_mine.hash = block_to_mine.get_hash()
# start mining
logger.info("MINING STARTED ")
cache.set('MINING', True)
mined_block = node.mine_block(block_to_mine)
cache.set('MINING', False)
logger.info("MINING ENDED ")
# If someone notified us about a new block, then stop minning
stop = cache.get('stop_minning')
if stop:
logger.info('Already got another block, Ignoring the newly Minned...')
cache.set('stop_minning', False)
return jsonify('Ignored'), 200
else:
# remove mined TXs from buffer
# remove added blocks from buffer
block_tx_ids = [t.transaction_id for t in mined_block.listOfTransactions]
node.tx_buffer = [t for t in node.tx_buffer if t.transaction_id not in block_tx_ids]
node.broadcast_block(mined_block)
node.chain.blocks.append(mined_block)
cache.set('node', node)
logger.info('Adding my Minned Block to my BlockChain')
return jsonify('OK'), 200
'''
Get a Node's blockchain
'''
@app.route('/blockchain')
def get_blockchain():
node = cache.get('node')
return jsonify({'blockchain': node.chain.serialize()}), 200
'''
Other Nodes POST this route to inform the Node that they found a new Block
remove the mined trascition
'''
@app.route('/block', methods=['POST'])
def post_block():
logger.info('/block POST Got a new Block!')
node = cache.get('node')
data = request.get_json()
blk = Block(**data['block'])
# Check if the block is valid, add it to the blockchain
if node.validate_block(blk):
logger.info('Adding the POSTed-Block in my BlockChain')
node.chain.blocks.append(blk)
# remove added blocks from buffer
block_tx_ids = [t.transaction_id for t in blk.listOfTransactions]
node.tx_buffer = [t for t in node.tx_buffer if t.transaction_id not in block_tx_ids]
# TODO: Stop the minning
node.refresh_wallet_from_chain()
cache.set('stop_minning', True)
cache.set('node', node)
return jsonify('Block added'), 200
else:
logger.warning("NOT a valid Block! Ignoring...")
return jsonify('Block ignored'), 200
# Else, we need to see if we must update our blockchain
resolve, blkc = node.resolve_conflicts()
if resolve:
cache.set('stop_minning', True)
node.chain = blkc
cache.set('node', node)
return jsonify('Coflict Resolved'), 200
@app.route('/balance')
def get_balance():
node = cache.get('node')
balance = node.wallet.balance()
return jsonify({'balance': balance}), 200
@app.route('/last-transactions')
def return_last_block_transactions():
node = cache.get('node')
return jsonify({'transactions': node.get_last_transactions()}), 200
@app.route('/hi')
def lets_get_hi():
# for debugging
return jsonify('Hi')
if __name__ == '__main__':
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-p', '--port', default=5000, type=int,
help='port to listen on')
parser.add_argument('-b', '--bootstrap', action='store_true',
help='if node is the bootstrap one')
parser.add_argument('-n', '--nodes_count', default=1, type=int,
help='number of nodes. Must have -b flag set')
args = parser.parse_args()
port = args.port
is_bootstrap = args.bootstrap
nodes_count = args.nodes_count
address = 'localhost:' + str(port)
# The state
node = Node(address)
cache.set('counter', 1)
cache.set('MINING', False)
cache.set('stop_minning', False)
cache.set('node', node)
cache.set('nodes_count', nodes_count)
# The Bootstrapping process
if not is_bootstrap:
# Regular nodes need to talk to bootstrap node first
logger.info('Notifying bootstrap')
data = {
'address': address,
'public_key': node.wallet.public_key.decode('utf-8'),
}
resp = requests.post('http://localhost:5000/nodes', json=data).json()
logger.info(resp)
else:
# The bootstrapper node cant post himself since he hasn't yet started
node.register_node_to_ring({
'address': address,
'public_key': node.wallet.public_key.decode('utf-8'),
'id': 0,
})
node.id = 0
cache.set('node', node)
logger.info('Bootstrapper ID: ' + str(node.id))
logger.info('Node initialized successfully!')
app.run(host='127.0.0.1', port=port)