10. Принцип добавления новой ноды и вектор нод.Ссылки:
-
Часть кода уже показана в предыдущем посте, тут он обновлен и добавлен новый. -
Видео для понимания, что такое векторные часы-
Создал телеграмм канал, ⚡присоединяйтесь⚡Создается в первой ноде генезис векторный файл node_vectorкоторый содержит информацию о нодах. Что значит векторный? Ниже опишу
directory = 'chain/config/'
filename = 'node_vector.atm'
if not os.path.exists(directory):
os.makedirs(directory)
# make genesis node vector number
atom_vector_data[1] = {
'node': 1,
'adress': node_adr,
'ipport': '192.168.0.100',
'active': True,
}
atom_vector_data[2] = {
'next_key' : randint(1, 1000000)
}
node.write_dict_file(atom_vector_data,directory,filename) # метод который записывает dict в файл в pickle формате
Тут мы создаем папку config в которой создаем файл node_vector.atm, в котором будут хранится данные в формате:
NODE 1 {номер ноды 1, ip ноды 1, публичный адрес ноды1;номер ноды 2, ip ноды 2, публичный адрес ноды2;номер ноды 3, ip ноды 3, публичный адрес ноды3;время обновления}
NODE 2 {номер ноды 1, ip ноды 1, публичный адрес ноды1;номер ноды 2, ip ноды 2, публичный адрес ноды2;номер ноды 3, ip ноды 3, публичный адрес ноды3;время обновления}
итд
Каждая нода когда будет соединяться с другой, будет получать вектор ее данных и отправлять свой. Если какие-то данные будут отличаться от текущих, они будут обновляться. Вектор нод считается валидным, когда у более 51% нод одинаковый вектор.
Следующая запись в векторе нод имеет только одну запись {next_key : randint(1, 1000000)}, тоесть переменную со случайным числом от 1 до 1000000.
Делается это для того, чтобы когда новая нода захочет присоединиться, она запрашивает векторный файл, получает последнее значение next_key, и нода отправляет свои данные на присоединение, и содержит в этих данных next_key и nonce, который создает 00 в начале хеша от этих данных.
Зачем это? Защита от подключения огромного количества левых или виртуальных нод. чтобы присоединиться новой ноде нужно сделать простой pow. если pow не подходит, другие ноды отвергают ее. Чем больше будет нод в системе, тем больше сложность pow.
Запрос через панель ноды в браузере@app.route('/zapros', methods=['GET', 'POST'])
def zapros():
# make geniesis block
zapros_adr = request.args.get('zapros_adr', 0, type=str)
base_node = request.args.get('base_node', 0, type=str)
ipport = request.args.get('ipport', 0, type=str)
result = node.try_connect_to_network(zapros_adr,ipport,base_node)
return jsonify(result=result)
Используется flask, забираем данные с frontend через ajax (с сайта получаем данные)
Метод try_connect_to_network соединяется с нодой указаной в base_node def try_connect_to_network(self,adr,ipport,base_node):
context = zmq.Context(1)
client = context.socket(zmq.REQ)
SERVER_ENDPOINT = 'tcp://'+base_node
client.connect(SERVER_ENDPOINT)
poll = zmq.Poller()
poll.register(client, zmq.POLLIN)
REQUEST_TIMEOUT = 2500
REQUEST_RETRIES = 3
retries_left = REQUEST_RETRIES
while retries_left:
data_send = {'addmeplz': '1','adr': adr, 'ip': ipport}
data_send = str(data_send).encode()
request = data_send
print("I: Пересылаю (%s)" % request)
client.send(request)
data = ''
expect_reply = True
while expect_reply:
socks = dict(poll.poll(REQUEST_TIMEOUT))
if socks.get(client) == zmq.POLLIN:
reply = client.recv()
reply = eval(reply.decode())
print(reply['send_hash'])
print(self.thishash(request))
if not reply:
data = 'break, not reply recive'
break
if reply['send_hash'].decode() == self.thishash(request):
print("I: Server replied (%s)" % reply)
retries_left = 0
data = 'answer: ' + reply['msg']
expect_reply = False
else:
print("E: Malformed reply from server: %s" % reply)
else:
data = 'no response'
print("W: No response from server, retrying…")
# Socket is confused. Close and remove it.
client.setsockopt(zmq.LINGER, 0)
client.close()
poll.unregister(client)
retries_left -= 1
if retries_left == 0:
print("E: Server seems to be offline, abandoning")
break
print("I: Reconnecting and resending (%s)" % request)
# Create new connection
client = context.socket(zmq.REQ)
client.connect(SERVER_ENDPOINT)
poll.register(client, zmq.POLLIN)
client.send(request)
return str(data)
метод отправляет словарь (dict) data_send = {'addmeplz': '1','adr': adr, 'ip': ipport} рабочей ноде, и получает обратно ответ в виде хеша, который проверяется хешированием отправленной информацией
if reply['send_hash'].decode() == self.thishash(request), если хеши совпадают, значит данные получены верно, можно обрабатывать их.
Рабочая нода(сервер), который получает данные, обрабатывает их:context = zmq.Context(1)
server = context.socket(zmq.REP)
server.bind("tcp://*:5555")
cycles = 0
while True:
request_clear = server.recv()
request = eval(request_clear.decode())
result_print = 'none'
if 'addmeplz' in request:
result = node.add_new_node(request['adr'],request['ip'])
result_print = result
send_hash = node.thishash(request_clear).encode()
send_rep = {
'send_hash' : send_hash,
'msg': result
}
print(result_print)
print(str(send_rep).encode())
server.send(str(send_rep).encode())
Рабочая нода получает данные, проверяет что за сообщение, и запускает метод add_new_node def add_new_node(self,adr,ipport):
node_vector = self.read_vector_file() # читай файл
for key, value in node_vector.items():
if adr in node_vector[key].values():
return 'This adr has in vector file, not add'
dlina = len(node_vector)
node_vector[dlina+1] = {
'node': dlina+1,
'adress': adr,
'ipport': ipport,
'active': False
}
self.write_dict_file(node_vector,'chain/config/','node_vector.atm') # обновляем файл
return 'Add in global vector file'
Тут реализовано добавление новых данных в вектор файл. Пока еще не реализован алгоритм pow для этого файла.
Уязвимости (будут дополнятся и выпиливаться):- надо решить вопрос с тем, чтобы нода не могла спамить левым вектором нод другим нодам, нарушая их работу. - тут будет ваша