Commit 0b9f5f21 authored by Romain Bignon's avatar Romain Bignon

discover network to connect to several nodes

parent 7d015edd
Pipeline #12 failed with stages
from ipaddress import ip_address
import hashlib
import struct
import time
from copy import deepcopy
from collections import OrderedDict
from ipaddress import ip_address
from termcolor import colored
......@@ -54,11 +56,15 @@ class MessageMetaClass(type):
class Message(metaclass=MessageMetaClass):
magic = 0xd9b4bef9
_fields = None
def __init__(self):
def __init__(self, *args, **kwargs):
self._fields = deepcopy(self._fields)
for name, value in kwargs.items():
setattr(self, name, value)
def __getattr__(self, name):
if name in self._fields:
return self._fields[name].get()
......@@ -79,13 +85,19 @@ class Message(metaclass=MessageMetaClass):
' ' if self._fields else '',
' '.join('%s=%s' % (name, colored(value.repr(), 'magenta')) for name, value in self._fields.items()))
def dump(self):
def serialize_payload(self):
data = b''
for field in self._fields.values():
data += field.data
return data
def serialize(self):
command = type(self).__name__.lower()
payload = self.serialize_payload()
checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[0:4]
return struct.pack('I12sI4s', self.magic, command.encode(), len(payload), checksum) + payload
@classmethod
def parse(cls, func):
def inner(self, data):
......@@ -93,7 +105,7 @@ class Message(metaclass=MessageMetaClass):
for field in msg._fields.values():
size = field.parse(data)
data = data[size:]
print(colored('<<<<<', 'green'), msg)
print(self.index, colored('<<<<<', 'green'), msg)
return func(self, msg)
return inner
......
from random import choice
import dns.resolver
from .log import log
class DNSSeed:
SERVERS = [
......@@ -16,6 +17,8 @@ class DNSSeed:
def find_nodes(self):
server = choice(self.SERVERS)
answers = dns.resolver.query(server, 'A')
with log('DNS seeding'):
answers = dns.resolver.query(server, 'A')
for rdata in answers:
yield rdata.address
from random import choice
import asyncio
from .dnsseed import DNSSeed
......@@ -5,28 +6,54 @@ from .node import OutboundNode
from .log import log
class Network:
min_nodes = 5
max_nodes = 10
def __init__(self):
self.nodes = []
async def start(self):
node = await self.connect_to_first_node()
if not node:
return
loop = asyncio.get_running_loop()
await self.connect_to_first_node()
while True:
await self.check_connectivity()
await asyncio.sleep(60)
async def check_connectivity(self):
for node in self.nodes:
if not node.transport:
self.nodes.remove(node)
if node.is_ping_needed():
node.send_ping()
if node.is_timed_out():
node.close()
self.nodes.remove(node)
if len(self.nodes) == 0:
await self.connect_to_first_node()
while len(self.nodes) < self.min_nodes:
node = choice(self.nodes)
for ipaddr, port in await node.get_nodes_addresses():
if len(self.nodes) < self.max_nodes:
await self.connect(str(ipaddr), port)
async def connect(self, ipaddr, port):
loop = asyncio.get_running_loop()
try:
with log('Trying to connect to %s:%s' % (ipaddr, port)):
transport, node = await loop.create_connection(lambda: OutboundNode(), ipaddr, port)
except OSError:
return None
self.nodes.append(node)
node.send_version()
await asyncio.sleep(3600)
import pdb; pdb.set_trace()
return node
async def connect_to_first_node(self):
dnsseed = DNSSeed()
loop = asyncio.get_running_loop()
for ipaddr in dnsseed.find_nodes():
try:
with log('Trying to connect to %s:8333' % ipaddr):
transport, protocol = await loop.create_connection(lambda: OutboundNode(), ipaddr, 8333)
except OSError:
continue
return protocol
if len(self.nodes) < self.min_nodes:
await self.connect(ipaddr, OutboundNode.default_port)
import asyncio
import hashlib
import random
import time
import struct
import hashlib
from termcolor import colored
from .protocol import Version, VerAck, Reject, Addr, Inv
from .protocol import Message, Version, VerAck, GetAddr, Reject, Addr, Inv, Ping, Pong
class Node(asyncio.Protocol):
magic = 0xd9b4bef9
default_port = 8333
header_size = 24
version = 70015
version_string = '/pytcoin:0.0/'
transport = None
queue = b''
index = 0
ping_wait = 60
def __init__(self):
self.transport = None
self.queue = b''
self.addresses_futures = []
self.index = Node.index
self.last_pong = int(time.time())
self.last_message = int(time.time())
Node.index += 1
self.loop = asyncio.get_running_loop()
self.handshake_made = self.loop.create_future()
def connection_made(self, transport):
self.transport = transport
self.send_version()
def connection_lost(self, exc):
print(self.index, colored('!!!!!', 'red'), 'Connection lost', colored(exc, 'yellow'))
self.transport = None
def data_received(self, data):
self.last_message = int(time.time())
self.queue += data
self.parse_read_buffer()
......@@ -26,8 +45,8 @@ class Node(asyncio.Protocol):
while len(self.queue) >= self.header_size:
magic, command, payload_len, checksum = struct.unpack('I12sI4s', self.queue[:self.header_size])
if magic != self.magic:
print('Oops, received a message from a non bitcoin client??')
if magic != Message.magic:
print('Oops, received a message from a non bitcoin client!?')
return
if len(self.queue) < self.header_size+payload_len:
......@@ -37,6 +56,10 @@ class Node(asyncio.Protocol):
payload = self.queue[self.header_size:self.header_size+payload_len]
self.queue = self.queue[self.header_size+payload_len:]
if checksum != hashlib.sha256(hashlib.sha256(payload).digest()).digest()[0:4]:
print('Oops, bad checksum!')
continue
command = command.decode().strip('\0')
try:
......@@ -53,8 +76,8 @@ class Node(asyncio.Protocol):
@VerAck.parse
def handle_verack(self, payload):
self.send_message('verack')
self.send_message('getaddr')
self.send_message(VerAck())
self.handshake_made.set_result(True)
@Reject.parse
def handle_reject(self, msg):
......@@ -62,21 +85,40 @@ class Node(asyncio.Protocol):
@Addr.parse
def handle_addr(self, msg):
pass
try:
future = self.addresses_futures.pop()
except IndexError:
# Perhaps we retrieved list of addresses without asking?
return
future.set_result(msg.addr_list)
@Inv.parse
def handle_inv(self, msg):
pass
def send_message(self, command, message=None):
print(colored('>>>>>', 'red'), message or ('<%s>' % command.title()))
data = self.make_message(command, message)
self.transport.write(data)
@Ping.parse
def handle_ping(self, msg):
pong = Pong()
pong.nonce = msg.nonce
self.send_message(Pong(nonce=msg.nonce))
@Pong.parse
def handle_pong(self, msg):
self.last_pong = int(time.time())
def is_ping_needed(self):
return self.last_message + self.ping_wait/2 < int(time.time())
def is_timed_out(self):
return self.last_pong + self.ping_wait < int(time.time())
def make_message(self, command, message):
payload = message.dump() if message else b''
checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[0:4]
return struct.pack('I12sI4s', self.magic, command.encode(), len(payload), checksum) + payload
def close(self):
self.transport.close()
self.transport = None
def send_message(self, message):
print(self.index, colored('>>>>>', 'red'), message)
self.transport.write(message.serialize())
def send_version(self):
msg = Version()
......@@ -89,7 +131,14 @@ class Node(asyncio.Protocol):
msg.user_agent = self.version_string
msg.start_height = 0
msg.relay = True
return self.send_message('version', msg)
return self.send_message(msg)
def get_nodes_addresses(self):
future = self.loop.create_future()
self.addresses_futures.append(future)
self.send_message(GetAddr())
return future
class OutboundNode(Node):
......
......@@ -16,6 +16,10 @@ class VerAck(Message):
pass
class GetAddr(Message):
pass
class Reject(Message):
message = VarStr()
ccode = Char()
......@@ -28,3 +32,11 @@ class Inv(Message):
class Addr(Message):
addr_list = List(TimeNetAddr)
class Ping(Message):
nonce = UInt64()
class Pong(Message):
nonce = UInt64()
#!/usr/bin/env python3
import sys
import asyncio
from pytcoin.net import Network
if __name__ == '__main__':
net = Network()
asyncio.run(net.start())
try:
asyncio.run(net.start())
except KeyboardInterrupt:
print('')
sys.exit(0)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment